{
  "openapi": "3.1.0",
  "info": {
    "title": "Bizik Webservice API",
    "version": "1.0.0",
    "description": "API امن بیزیک شامل دو حوزه مستقل است: ۱) ارسال و دریافت پیامک، OTP و گزارش دلیوری؛ ۲) دریافت خرید تکمیل‌شده از فروشگاه/CRM برای اتوماسیون. اتصال تجارت به‌تنهایی پیامک ارسال نمی‌کند. همه قابلیت‌ها Scope مستقل، تأیید مدیریت و عملیات نوشتنی تکرارپذیر امن دارند. کنترل IP ثابت به‌صورت پیش‌فرض اجباری است و فقط مدیریت می‌تواند آن را برای یک کلید غیراجباری کند. محدودیت کشور IP مستقل است و به‌صورت پیش‌فرض فقط ایران را می‌پذیرد. سقف اولیه پیامک عادی ۳۰۰ گیرنده در ساعت و ۲۰۰۰ گیرنده در روز است؛ سقف ساعتی به صف تأیید و سقف روزانه به رد درخواست منجر می‌شود."
  },
  "servers": [
    { "url": "https://webservice.bizik.ir/v1", "description": "Production (پس از راه‌اندازی دامنه)" },
    { "url": "http://127.0.0.1:3000/v1", "description": "Local development" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Service", "description": "سلامت و دسترس‌پذیری سرویس" },
    { "name": "Messaging", "description": "ارسال پیامک تکی و گروهی. API فایل خام Excel/CSV نمی‌گیرد؛ فایل باید در سامانه فراخوان به آرایه‌های حداکثر ۱۰۰۰ شماره تبدیل شود." },
    { "name": "OTP", "description": "برآورد، ارسال و بررسی رمز یک‌بارمصرف با قالب تأییدشده" },
    { "name": "Messaging reports", "description": "مانده، وضعیت پردازش درخواست، دلیوری پیامک و پیام‌های ورودی" },
    { "name": "Commerce automation", "description": "دریافت داده فروشگاه/ERP/CRM برای اتوماسیون. نسخه فعلی فقط رویداد order.completed را می‌پذیرد و ثبت رویداد به‌تنهایی تضمین ارسال پیامک نیست." }
  ],
  "paths": {
    "/text/review": {
      "post": {
        "summary": "بررسی رایگان متن؛ مجوز text:review",
        "description": "بدون ارسال پیامک یا کسر کیف پول. حداکثر ۴۰۰۰ کاراکتر. سقف پیش‌فرض حساب ۱۰ در دقیقه و ۱۰۰ در ۲۴ ساعت؛ قابل تنظیم توسط مدیر اصلی. نتیجه سبز مجوز ارسال یا تأیید لینک نیست. خطای هوش مصنوعی unknown است.",
        "parameters": [{"in":"header","name":"Idempotency-Key","required":true,"schema":{"type":"string","minLength":8,"maxLength":128}}],
        "requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":4000}}}}}},
        "responses": {
          "200":{"description":"موضوع، خطر و اقدام مستقل؛ سبز مجوز ارسال نیست","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"topics":{"type":"array","items":{"type":"string","enum":["phishing","fraud","threat","abuse","misinformation","gathering","political","political_names"]}},"recommended_action":{"enum":["continue_checks","manual_review","hold"]},"matched_terms":{"type":"array","items":{"type":"string"}},"risk":{"enum":["green","yellow","red"]},"reason":{"type":"string"},"flagged_words":{"type":"array","items":{"type":"string"}},"review_required":{"type":"boolean"},"cost":{"const":0},"send_authorized":{"const":false},"requestId":{"type":"string"}}}}}},
          "400":{"description":"متن یا شناسه درخواست نامعتبر"},"401":{"description":"کلید نامعتبر"},"403":{"description":"مجوز یا دسترسی غیرفعال"},"409":{"description":"درخواست تکراری متفاوت یا در حال بررسی"},"429":{"description":"عبور از سقف مصرف حساب یا کلید"},"503":{"description":"بررسی در دسترس نیست؛ risk=unknown و review_required=true"}
        }
      }
    },
    "/health": {
      "get": {
        "tags": ["Service"],
        "summary": "سلامت وب‌سرویس",
        "operationId": "webserviceHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "service", "version"],
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "service": { "type": "string", "const": "bizik-webservice" },
                    "version": { "type": "string", "const": "v1" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/messages/quote": {
      "post": {
        "tags": ["Messaging"],
        "summary": "برآورد هزینه پیامک تکی",
        "operationId": "quoteSingleMessage",
        "x-scope": "sms:single:send",
        "parameters": [{ "$ref": "#/components/parameters/OperationalTimestamp" }, { "$ref": "#/components/parameters/OperationalSignature" }],
        "requestBody": { "$ref": "#/components/requestBodies/SingleMessage" },
        "responses": { "200": { "$ref": "#/components/responses/Quote" } }
      }
    },
    "/messages": {
      "post": {
        "tags": ["Messaging"],
        "summary": "ثبت سفارش پیامک تکی",
        "description": "هدر Idempotency-Key الزامی است و costConfirmation باید دقیقاً با quote برابر باشد. پاسخ 201 یعنی ارسال خودکار به اپراتور انجام شده؛ پاسخ 202 یعنی سفارش در صف تأیید یا آماده ارسال دستی است. عبور از سقف روزانه پاسخ 429 می‌دهد.",
        "operationId": "sendSingleMessage",
        "x-scope": "sms:single:send",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }, { "$ref": "#/components/parameters/OperationalTimestamp" }, { "$ref": "#/components/parameters/OperationalSignature" }],
        "requestBody": { "$ref": "#/components/requestBodies/SingleMessageWithConfirmation" },
        "responses": { "201": { "$ref": "#/components/responses/AcceptedMessage" }, "202": { "$ref": "#/components/responses/AcceptedMessage" }, "402": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" }, "502": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/messages/batch/quote": {
      "post": {
        "tags": ["Messaging"],
        "summary": "برآورد هزینه پیامک گروهی تا ۱۰۰۰ شماره",
        "description": "برای ارسال مبتنی بر Excel/CSV، فایل را در سرور خود پردازش و شماره‌ها را در recipients بفرستید؛ این endpoint فایل خام یا multipart دریافت نمی‌کند.",
        "operationId": "quoteBatchMessage",
        "x-scope": "sms:batch:send",
        "requestBody": { "$ref": "#/components/requestBodies/BatchMessage" },
        "responses": { "200": { "$ref": "#/components/responses/Quote" } }
      }
    },
    "/messages/batch": {
      "post": {
        "tags": ["Messaging"],
        "summary": "ثبت سفارش پیامک گروهی تا ۱۰۰۰ شماره",
        "description": "پاسخ 201 یعنی ارسال خودکار به اپراتور انجام شده؛ پاسخ 202 یعنی سفارش در صف تأیید یا آماده ارسال دستی است. عبور از سقف روزانه پاسخ 429 می‌دهد.",
        "operationId": "sendBatchMessage",
        "x-scope": "sms:batch:send",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "$ref": "#/components/requestBodies/BatchMessageWithConfirmation" },
        "responses": { "201": { "$ref": "#/components/responses/AcceptedMessage" }, "202": { "$ref": "#/components/responses/AcceptedMessage" }, "402": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" }, "502": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/account/balance": {
      "get": {
        "tags": ["Messaging reports"],
        "summary": "مانده اعتبار قابل استفاده برای پیامک و OTP",
        "description": "مبلغ به تومان برگردانده می‌شود. تعداد پیامک ثابت نیست و به متن، اپراتور گیرنده و نوع خط فرستنده بستگی دارد.",
        "operationId": "getSmsWalletBalance",
        "x-scope": "reports:read",
        "responses": {
          "200": {
            "description": "Current SMS wallet balance",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Balance" } } }
          },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/otp/quote": {
      "post": {
        "tags": ["OTP"],
        "summary": "برآورد هزینه OTP",
        "operationId": "quoteOtp",
        "x-scope": "otp:send",
        "requestBody": { "$ref": "#/components/requestBodies/Otp" },
        "responses": { "200": { "$ref": "#/components/responses/Quote" } }
      }
    },
    "/otp/send": {
      "post": {
        "tags": ["OTP"],
        "summary": "ارسال کد OTP شش‌رقمی با اعتبار دو دقیقه",
        "description": "templateId باید متعلق به همان حساب و مورد تأیید مدیریت باشد. زیر سقف ارسال فوری است؛ بالاتر از سقف ساعتی/روزانه پاسخ 202 و صف تأیید مدیر است. کد و مهلت دو دقیقه‌ای فقط هنگام تأیید تولید می‌شوند. کد خام در پاسخ و دیتابیس ذخیره نمی‌شود. رد مدیر یا خطای Provider هزینه را برمی‌گرداند. سقف خدماتی اشتراکی قطعی است.",
        "operationId": "sendOtp",
        "x-scope": "otp:send",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "$ref": "#/components/requestBodies/OtpWithConfirmation" },
        "responses": { "201": { "description": "OTP sent", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtpSent" } } } }, "202": { "description": "Awaiting manager approval; no code or expiry yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtpQueued" } } } }, "402": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" }, "502": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/otp/verify": {
      "post": {
        "tags": ["OTP"],
        "summary": "بررسی OTP",
        "operationId": "verifyOtp",
        "x-scope": "otp:verify",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtpVerifyRequest" } } }
        },
        "responses": { "200": { "description": "Verified" }, "400": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" }, "410": { "$ref": "#/components/responses/Error" }, "423": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/requests/{requestId}": {
      "get": {
        "tags": ["Messaging reports"],
        "summary": "وضعیت درخواست یا سفارش",
        "operationId": "getRequestStatus",
        "x-scope": "reports:read",
        "parameters": [{ "name": "requestId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "Request status" }, "404": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/messages/{requestId}/delivery": {
      "get": {
        "tags": ["Messaging reports"],
        "summary": "گزارش دلیوری گیرندگان درخواست",
        "operationId": "getMessageDelivery",
        "x-scope": "delivery:read",
        "parameters": [{ "name": "requestId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "Delivery status and recipient logs" }, "404": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/inbound/messages": {
      "get": {
        "tags": ["Messaging reports"],
        "summary": "دریافت پیامک‌های ورودی متعلق به حساب",
        "operationId": "listInboundMessages",
        "x-scope": "inbound:read",
        "parameters": [
          { "name": "afterId", "in": "query", "schema": { "type": "integer", "minimum": 0, "default": 0 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } },
          { "name": "toNumber", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Inbound messages and nextAfterId cursor" } }
      }
    },
    "/commerce/integrations": {
      "get": {
        "tags": ["Commerce automation"],
        "summary": "فهرست اتصال‌های فروشگاه متعلق به حساب",
        "operationId": "listCommerceIntegrations",
        "x-scope": "commerce:read",
        "responses": { "200": { "description": "Commerce integrations" }, "500": { "$ref": "#/components/responses/Error" } }
      },
      "post": {
        "tags": ["Commerce automation"],
        "summary": "ساخت اتصال فروشگاه",
        "description": "اتصال فقط برای حساب مالک API Key ساخته می‌شود. شناسه پاسخ را در رویدادهای سفارش بفرستید.",
        "operationId": "createCommerceIntegration",
        "x-scope": "commerce:write",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommerceIntegrationRequest" } } } },
        "responses": { "201": { "description": "Integration created" }, "400": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/commerce/events/order-completed": {
      "post": {
        "tags": ["Commerce automation"],
        "summary": "ثبت خرید تکمیل‌شده مشتری",
        "description": "ورودی داده اتوماسیون است، نه مسیر ارسال مستقیم پیامک. نسخه فعلی فقط order.completed را می‌پذیرد. هم Idempotency-Key درخواست و هم eventId رویداد باید پایدار باشند. userId از API Key تعیین می‌شود و از بدنه پذیرفته نمی‌شود. ارسال احتمالی فقط تابع اتوماسیون فعال و منطبق در پنل است.",
        "operationId": "recordCompletedCommerceOrder",
        "x-scope": "commerce:write",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommerceCompletedOrderRequest" } } } },
        "responses": {
          "200": { "description": "The same event was already processed" },
          "201": { "description": "Completed order processed" },
          "400": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "bzk_live_...", "description": "کلید فقط یک بار هنگام ایجاد نمایش داده می‌شود. کنترل IP به‌صورت پیش‌فرض اجباری است؛ IP جدید برای تأیید مدیر ثبت می‌شود و مدیریت می‌تواند برای سرویس‌های دارای IP متغیر این کنترل را غیراجباری کند." }
    },
    "parameters": {
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string", "minLength": 8, "maxLength": 128 }, "description": "برای هر عملیات یک مقدار پایدار و یکتا بفرستید." },
      "OperationalTimestamp": { "name": "X-Bizik-Operational-Timestamp", "in": "header", "required": false, "schema": { "type": "string", "pattern": "^[0-9]{13}$" }, "description": "فقط همراه operationalType الزامی است؛ زمان Unix بر حسب میلی‌ثانیه با حداکثر دو دقیقه اختلاف." },
      "OperationalSignature": { "name": "X-Bizik-Operational-Signature", "in": "header", "required": false, "schema": { "type": "string", "pattern": "^v1=[a-f0-9]{64}$" }, "description": "فقط همراه operationalType الزامی است؛ HMAC-SHA256 امضاشده با API Key سروری روی زمان، روش، مسیر، Idempotency-Key و JSON canonical بدنه." }
    },
    "schemas": {
      "CostConfirmation": { "type": "object", "required": ["confirmed", "costAmount"], "properties": { "confirmed": { "type": "boolean", "const": true }, "costAmount": { "type": "integer", "minimum": 0 } } },
      "SingleMessageRequest": { "type": "object", "required": ["to", "text"], "properties": { "to": { "type": "string", "pattern": "^09[0-9]{9}$" }, "text": { "type": "string", "minLength": 1, "maxLength": 4000 }, "sender": { "type": "string" }, "operationalType": { "type": "string", "enum": ["coach_class_enrollment", "coach_class_registration", "coach_attendance_link", "coach_attendance_result", "coach_attendance_reminder", "coach_class_renewal", "coach_class_cancellation", "coach_course_completion", "coach_makeup_session", "coach_settlement", "coach_manager_registration"], "description": "فقط برای کلید دارای scope مستقل sms:transactional:send؛ این رویدادهای عملیاتی تک‌گیرنده بدون پسوند تبلیغاتی ارسال می‌شوند." }, "costConfirmation": { "$ref": "#/components/schemas/CostConfirmation" } } },
      "BatchMessageRequest": { "type": "object", "required": ["recipients", "text"], "properties": { "recipients": { "type": "array", "minItems": 1, "maxItems": 1000, "uniqueItems": true, "items": { "type": "string", "pattern": "^09[0-9]{9}$" } }, "text": { "type": "string", "minLength": 1, "maxLength": 4000 }, "sender": { "type": "string" }, "costConfirmation": { "$ref": "#/components/schemas/CostConfirmation" } } },
      "OtpRequest": { "type": "object", "required": ["to", "templateId"], "properties": { "to": { "type": "string", "pattern": "^09[0-9]{9}$" }, "templateId": { "type": "integer", "minimum": 1, "description": "شناسه قالب OTP تأییدشده مدیریت" }, "templateText": { "type": "string", "minLength": 10, "maxLength": 500, "description": "اختیاری؛ باید دقیقاً یک {code} داشته باشد. اگر با قالب تأییدشده متفاوت باشد، ارسال تا تأیید مدیر در صف می‌ماند." }, "sender": { "type": "string" }, "costConfirmation": { "$ref": "#/components/schemas/CostConfirmation" } } },
      "OtpVerifyRequest": { "type": "object", "required": ["challengeId", "code"], "properties": { "challengeId": { "type": "string", "format": "uuid" }, "code": { "type": "string", "pattern": "^[0-9]{6}$" } } },
      "SmsTextAnalysis": { "type": "object", "description": "تحلیل متن نهایی ارسالی؛ پیامک عادی شامل لغو۱۱ است. OTP فقط در صورت GSM-7 بودن تمام متن نهایی تعرفه لاتین می‌گیرد.", "properties": { "encoding": { "type": "string", "enum": ["GSM-7", "Unicode"] }, "tariffType": { "type": "string", "enum": ["latin", "unicode"] }, "total": { "type": "integer", "description": "طول متن بر حسب واحد UTF-16" }, "encodedUnits": { "type": "integer" }, "parts": { "type": "integer", "minimum": 1 }, "singleLimit": { "type": "integer", "enum": [160, 70] }, "multipartLimit": { "type": "integer", "enum": [153, 67] } } },
      "Quote": { "type": "object", "properties": { "success": { "type": "boolean" }, "quote": { "type": "boolean" }, "costAmount": { "type": "integer" }, "currency": { "type": "string", "const": "IRT" }, "smsUnits": { "type": "integer" }, "textAnalysis": { "$ref": "#/components/schemas/SmsTextAnalysis" }, "pricingBreakdown": { "type": "array", "items": { "type": "object", "properties": { "operator": { "type": "string" }, "label": { "type": "string" }, "rate": { "type": "integer" }, "recipients": { "type": "integer" }, "smsUnits": { "type": "integer" }, "cost": { "type": "integer" } } } } } },
      "AcceptedMessage": { "type": "object", "properties": { "success": { "type": "boolean" }, "requestId": { "type": "string", "format": "uuid" }, "orderId": { "type": "integer" }, "status": { "type": "string", "enum": ["pending_approval", "approved", "sent", "send_failed"] }, "costAmount": { "type": "integer" }, "approvalReason": { "type": ["string", "null"] }, "limits": { "type": "object" } } },
      "OtpSent": { "type": "object", "properties": { "success": { "type": "boolean" }, "requestId": { "type": "string", "format": "uuid" }, "challengeId": { "type": "string", "format": "uuid" }, "expiresInSeconds": { "type": "integer", "const": 120 } } },
      "OtpQueued": { "type": "object", "properties": { "success": { "type": "boolean", "const": true }, "requestId": { "type": "string", "format": "uuid" }, "challengeId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "const": "pending_approval" }, "expiresInSeconds": { "type": "null" }, "costAmount": { "type": "integer" }, "securityReview": { "type": "object" } } },
      "Balance": { "type": "object", "required": ["success", "walletBalance", "currency"], "properties": { "success": { "type": "boolean", "const": true }, "walletBalance": { "type": "number", "minimum": 0 }, "currency": { "type": "string", "const": "IRT" }, "message": { "type": "string" } } },
      "CommerceIntegrationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "type"],
        "properties": {
          "name": { "type": "string", "minLength": 2, "maxLength": 120 },
          "type": { "type": "string", "enum": ["woocommerce", "shopify", "prestashop", "magento", "custom_api", "erp"] }
        }
      },
      "CommerceCustomer": {
        "type": "object",
        "required": ["mobile"],
        "properties": {
          "mobile": { "type": "string", "description": "شماره موبایل ایران؛ قالب‌های 09، 98 و 0098 پذیرفته و یکسان‌سازی می‌شوند." },
          "firstName": { "type": "string", "maxLength": 50 },
          "lastName": { "type": "string", "maxLength": 50 },
          "email": { "type": "string", "format": "email", "maxLength": 200 }
        }
      },
      "CommerceOrder": {
        "type": "object",
        "required": ["externalId", "total"],
        "properties": {
          "externalId": { "type": "string", "minLength": 1, "maxLength": 191 },
          "currency": { "type": "string", "maxLength": 12, "default": "IRT" },
          "subtotal": { "type": "integer", "minimum": 0 },
          "discountTotal": { "type": "integer", "minimum": 0 },
          "shippingTotal": { "type": "integer", "minimum": 0 },
          "taxTotal": { "type": "integer", "minimum": 0 },
          "total": { "type": "integer", "minimum": 0 },
          "paymentMethod": { "type": "string", "maxLength": 100 },
          "orderedAt": { "type": "string", "format": "date-time" },
          "paidAt": { "type": "string", "format": "date-time" },
          "completedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CommerceOrderItem": {
        "type": "object",
        "required": ["productName", "quantity"],
        "properties": {
          "productExternalId": { "type": "string", "maxLength": 191 },
          "productName": { "type": "string", "maxLength": 500 },
          "sku": { "type": "string", "maxLength": 191 },
          "category": { "type": "string", "maxLength": 191 },
          "quantity": { "type": "integer", "minimum": 1 },
          "unitPrice": { "type": "integer", "minimum": 0 },
          "lineTotal": { "type": "integer", "minimum": 0 }
        }
      },
      "CommerceCompletedOrderRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["integrationId", "eventId", "customer", "order"],
        "properties": {
          "integrationId": { "type": "integer", "minimum": 1 },
          "eventId": { "type": "string", "minLength": 1, "maxLength": 191 },
          "occurredAt": { "type": "string", "format": "date-time" },
          "customer": { "$ref": "#/components/schemas/CommerceCustomer" },
          "order": { "$ref": "#/components/schemas/CommerceOrder" },
          "items": { "type": "array", "maxItems": 1000, "items": { "$ref": "#/components/schemas/CommerceOrderItem" } }
        }
      },
      "Error": {
        "type": "object",
        "required": ["success", "code", "error"],
        "properties": {
          "success": { "type": "boolean", "const": false },
          "code": {
            "type": "string",
            "description": "کد پایدار و قابل پردازش خطا",
            "examples": [
              "API_KEY_REQUIRED", "API_KEY_INVALID", "API_KEY_INACTIVE", "ACCOUNT_BLOCKED",
              "IP_NOT_ALLOWED", "IP_COUNTRY_NOT_IRAN", "API_RATE_LIMITED", "API_AUTH_UNAVAILABLE", "SCOPE_REQUIRED",
              "IDEMPOTENCY_KEY_REQUIRED", "IDEMPOTENCY_CONFLICT", "REQUEST_IN_PROGRESS",
              "SMS_COST_CONFIRMATION_REQUIRED", "INSUFFICIENT_WALLET_BALANCE",
              "OTP_INPUT_INVALID", "OTP_TEMPLATE_REQUIRED", "OTP_TEMPLATE_NOT_APPROVED",
              "OTP_CONTENT_RESTRICTED", "OTP_RATE_LIMITED", "OTP_PROVIDER_FAILED",
              "OTP_NOT_FOUND", "OTP_EXPIRED", "OTP_LOCKED", "OTP_INVALID", "OTP_PENDING_APPROVAL", "OTP_REJECTED",
              "REQUEST_ID_INVALID", "REQUEST_NOT_FOUND", "REPORT_FAILED",
              "DELIVERY_NOT_FOUND", "DELIVERY_REPORT_FAILED", "DESTINATION_INVALID",
              "LIMIT_INVALID", "CURSOR_INVALID", "INBOUND_REPORT_FAILED",
              "BATCH_PROVIDER_UNSUPPORTED", "SHARED_SERVICE_DAILY_LIMIT", "WEBSERVICE_SMS_DAILY_LIMIT",
              "ACCOUNT_NOT_FOUND", "BALANCE_FAILED", "SMS_QUOTE_FAILED", "SMS_ORDER_FAILED",
              "OTP_QUOTE_FAILED", "OTP_SEND_FAILED", "OTP_VERIFY_FAILED",
              "COMMERCE_INTEGRATION_NAME_INVALID", "COMMERCE_INTEGRATION_TYPE_INVALID",
              "COMMERCE_USER_ID_FORBIDDEN",
              "COMMERCE_INTEGRATIONS_FAILED", "COMMERCE_INTEGRATION_FAILED",
              "COMMERCE_VALIDATION_ERROR", "COMMERCE_EVENT_FAILED", "UNSUPPORTED_EVENT",
              "INTEGRATION_NOT_FOUND", "EVENT_ID_CONFLICT"
            ]
          },
          "detectedIp": { "type": "string", "description": "IP خروجی شناسایی‌شده؛ فقط در پاسخ‌های مرتبط با کنترل IP" },
          "ipApprovalStatus": { "type": "string", "enum": ["pending", "not_queued"], "description": "وضعیت ثبت IP برای بررسی مدیریت" },
          "error": { "type": "string", "description": "پیام فارسی مناسب نمایش یا ثبت کنترل‌شده" }
        }
      }
    },
    "requestBodies": {
      "SingleMessage": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SingleMessageRequest" } } } },
      "SingleMessageWithConfirmation": { "$ref": "#/components/requestBodies/SingleMessage" },
      "BatchMessage": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchMessageRequest" } } } },
      "BatchMessageWithConfirmation": { "$ref": "#/components/requestBodies/BatchMessage" },
      "Otp": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtpRequest" } } } },
      "OtpWithConfirmation": { "$ref": "#/components/requestBodies/Otp" }
    },
    "responses": {
      "Quote": { "description": "Cost quote", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Quote" } } } },
      "AcceptedMessage": { "description": "Order accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AcceptedMessage" } } } },
      "Error": { "description": "Stable error response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
