{
  "openapi": "3.1.0",
  "info": {
    "title": "SynQ AI — Company API",
    "version": "1.0.0",
    "description": "Server-to-server API for a SynQ AI company: create and find customers, move them\nthrough the pipeline, open a dialog on a specific agent, and send messages to the\ncustomer's messenger.\n\nEvery request carries a company key:\n\n    Authorization: Bearer sk_live_...\n\nThe key is minted by the company owner in the CRM (company settings → API tab)\nand shown exactly once. It is secret and server-to-server only — never put it in\na browser or any client-side code. Each key carries its own set of scopes; an\nendpoint that needs a scope the key lacks answers 403 `insufficient_scope`.\n\nEvery error arrives in one envelope:\n\n    {\"error\": {\"code\": \"wa_window_closed\", \"message\": \"...\", \"details\": {}}}\n\nRate limit: 120 requests per 60 seconds per key. What is left is reported in the\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers.\n\nIdempotency: `POST /customers` and `POST /customers/{id}/messages` accept an\n`Idempotency-Key` header. A repeat with the same key within 24 hours returns the\nstored response without performing the operation again.\n",
    "contact": {
      "name": "SynQ AI",
      "url": "https://synq.software"
    }
  },
  "servers": [
    {
      "url": "https://api.synq.software/ext"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/agents": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Список агентов",
        "operationId": "list_agents_api_v1_ext_agents_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/AgentOut"
                  },
                  "type": "array",
                  "title": "Response List Agents Api V1 Ext Agents Get"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "agents:read"
        ]
      }
    },
    "/customers": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Создать / upsert клиента",
        "operationId": "create_customer_api_v1_ext_customers_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtCustomerCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtCustomerCreateOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/customers/lookup": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Найти клиента по мессенджеру / телефону / email",
        "description": "Find a customer without knowing its id — by messenger identity\n(``messenger_type`` + ``messenger_id``), phone, or email. Read-only\ncounterpart to the upsert on ``POST /customers``. Registered BEFORE the\n``/{customer_id}`` route so \"lookup\" isn't parsed as a UUID id.",
        "operationId": "lookup_customer_api_v1_ext_customers_lookup_get",
        "parameters": [
          {
            "name": "messenger_type",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Messenger Type"
            }
          },
          {
            "name": "messenger_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Messenger Id"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Phone"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Email"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtCustomerGetOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      }
    },
    "/customers/{customer_id}": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Клиент + текущий этап + мессенджеры",
        "operationId": "get_customer_api_v1_ext_customers__customer_id__get",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtCustomerGetOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      }
    },
    "/customers/{customer_id}/conversations": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Создать-или-найти диалог клиента на агенте",
        "description": "Open a dialog with the customer on a specific agent, without messaging them.\n\nMirrors the CRM's own «operator writes first» flow (``POST /api/conversations``)\nfor server-to-server callers: the client card in the CRM gets a chat an\noperator can open and type into, and nothing is delivered to the messenger.\n\n**Idempotent by construction** — an ACTIVE conversation for this\n(customer, agent) is returned as-is with ``created:false``; a second call\nnever opens a parallel dialog and never re-stamps the timeline. Callers that\nalso want retry-safety on the *transport* can pass ``Idempotency-Key``.\n\nA freshly created conversation gets one system message\n(«Conversation created via API»). Without it the dialog has no\n``last_message_at`` and sinks to the bottom of the operator's list — which\nreads as if it were never created.",
        "operationId": "create_customer_conversation_api_v1_ext_customers__customer_id__conversations_post",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationCreateIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An active dialog on this agent already existed — returned as-is",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationCreateOut"
                }
              }
            }
          },
          "201": {
            "description": "Dialog created"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/customers/{customer_id}/deals": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Сделки клиента (любой статус, опц. фильтр по воронке)",
        "description": "List ALL deals of the customer — including closed (won/lost) ones, so a\ncaller can check «уже проходил воронку» without guessing from the single\nactive-deal view on GET /customers/{id}.",
        "operationId": "list_customer_deals_api_v1_ext_customers__customer_id__deals_get",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          },
          {
            "name": "pipeline_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Pipeline Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DealsListOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      },
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Создать сделку с явным placement",
        "description": "Create a deal on an explicit pipeline/stage (mirror of the customer\nplacement semantics). 409 when the customer already has an ACTIVE deal in\nthe target pipeline — one active deal per funnel.",
        "operationId": "create_customer_deal_api_v1_ext_customers__customer_id__deals_post",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DealCreateIn"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtDealOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/customers/{customer_id}/merge": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Слить в эту карточку вторую (по id или по промокоду)",
        "description": "Свести две карточки одного человека в одну.\n\nЗачем: человек получил промокод в Instagram, а разговаривает в Telegram —\nдля нас это две карточки, и единственное, что их связывает, это код.\nПоэтому донора можно указать промокодом: внешний сервис знает код, а не\nнаши id.\n\nПобедитель — карточка из пути. Его заполненные поля не переписываются;\nиз донора берётся только то, чего у победителя нет. Что не удалось\nперенести из-за уникальных индексов (второй код из того же пула, вторая\nактивная сделка в одной воронке), возвращается списком ``warnings`` —\nмолчаливое «ок» здесь означало бы потерю данных.\n\nПовторный вызов безопасен: уже слитый донор возвращает\n``already_merged: true`` и ничего не двигает.",
        "operationId": "merge_customer_api_v1_ext_customers__customer_id__merge_post",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerMergeIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerMergeOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/customers/{customer_id}/messages": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Отправить сообщение клиенту (free-form / template)",
        "operationId": "send_message_api_v1_ext_customers__customer_id__messages_post",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageSendIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSendOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{customer_id}/messengers": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Мессенджеры клиента + окно отправки",
        "operationId": "get_customer_messengers_api_v1_ext_customers__customer_id__messengers_get",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessengersListOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      }
    },
    "/customers/{customer_id}/stage": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Сменить этап активной сделки",
        "operationId": "change_stage_api_v1_ext_customers__customer_id__stage_post",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StageChangeIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StageChangeOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/customers/{customer_id}/variables": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Переменные клиента",
        "operationId": "get_customer_variables_api_v1_ext_customers__customer_id__variables_get",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VariablesOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      },
      "patch": {
        "tags": [
          "External API"
        ],
        "summary": "Записать переменные клиента",
        "operationId": "patch_customer_variables_api_v1_ext_customers__customer_id__variables_patch",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Customer Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VariablesUpdateIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VariablesOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/deals/{deal_id}/close": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Закрыть сделку (won / lost)",
        "operationId": "close_deal_api_v1_ext_deals__deal_id__close_post",
        "parameters": [
          {
            "name": "deal_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Deal Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DealCloseIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtDealOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/deals/{deal_id}/variables": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Переменные сделки",
        "operationId": "get_deal_variables_api_v1_ext_deals__deal_id__variables_get",
        "parameters": [
          {
            "name": "deal_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Deal Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VariablesOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:read"
        ]
      },
      "patch": {
        "tags": [
          "External API"
        ],
        "summary": "Записать переменные сделки",
        "operationId": "patch_deal_variables_api_v1_ext_deals__deal_id__variables_patch",
        "parameters": [
          {
            "name": "deal_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Deal Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VariablesUpdateIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VariablesOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "customers:write"
        ]
      }
    },
    "/pipelines": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Список воронок",
        "operationId": "list_pipelines_api_v1_ext_pipelines_get",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Agent Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PipelineOut"
                  },
                  "title": "Response List Pipelines Api V1 Ext Pipelines Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "pipelines:read"
        ]
      }
    },
    "/promo-codes/{code}": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Кому выдан код",
        "description": "Найти клиента по выданному коду.\n\nВнешний сервис опознаёт человека своим способом (обычно по Telegram), а код\nмог уйти в другом мессенджере. Код — единственный общий ключ между двумя\nсистемами, поэтому именно он и работает мостом.\n\nИщем по всем пулам компании: вызывающий знает код, но не обязан знать, из\nкакого он пула.",
        "operationId": "lookup_promo_code_api_v1_ext_promo_codes__code__get",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoCodeLookupOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:read"
        ]
      }
    },
    "/promo-pools": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Пулы промокодов компании (с остатком)",
        "operationId": "list_promo_pools_api_v1_ext_promo_pools_get",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Только пулы этого агента",
              "title": "Agent Id"
            },
            "description": "Только пулы этого агента"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoPoolsListOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:read"
        ]
      }
    },
    "/promo-pools/{pool_id}": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Один пул: остаток и порог алерта",
        "operationId": "get_promo_pool_api_v1_ext_promo_pools__pool_id__get",
        "parameters": [
          {
            "name": "pool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Pool Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoPoolOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:read"
        ]
      },
      "patch": {
        "tags": [
          "External API"
        ],
        "summary": "Настройки пула (в т.ч. выключить раздачу)",
        "description": "Поменять настройки пула. Переданы только нужные поля — остальные не трогаются.\n\n``is_enabled: false`` — самый быстрый способ остановить раздачу целиком:\nвыключенный пул не выдаёт код вообще, и агент сразу переходит на\n``exhausted_instruction``. Коды при этом остаются на месте, включить\nобратно — тем же вызовом.",
        "operationId": "update_promo_pool_api_v1_ext_promo_pools__pool_id__patch",
        "parameters": [
          {
            "name": "pool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Pool Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PromoPoolUpdateIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoPoolOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:write"
        ]
      }
    },
    "/promo-pools/{pool_id}/codes": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Коды пула (история выдач)",
        "operationId": "list_promo_codes_api_v1_ext_promo_pools__pool_id__codes_get",
        "parameters": [
          {
            "name": "pool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Pool Id"
            }
          },
          {
            "name": "issued_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Только выданные",
              "default": false,
              "title": "Issued Only"
            },
            "description": "Только выданные"
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Поиск по коду",
              "title": "Search"
            },
            "description": "Поиск по коду"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 500,
              "minimum": 1,
              "default": 100,
              "title": "Limit"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0,
              "title": "Offset"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoCodesListOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:read"
        ]
      },
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Досыпать коды в пул",
        "description": "Долив кодов. Повтор того же списка безопасен.\n\nДедуп двухслойный и оба слоя уже в загрузчике: внутри присланного списка —\nпо множеству, против уже залитых — уникальным индексом ``(pool_id, code)``\nс ``ON CONFLICT DO NOTHING``. Поэтому отдельной идемпотентности по\nзаголовку здесь не нужно: повторный вызов вернёт ``accepted: 0``, а не\nудвоит пул. Доливка встаёт в ХВОСТ очереди — уже загруженные раздаются\nпервыми.\n\nЗагрузчик заодно снимает флаги алертов, если остаток снова выше порога:\nбез этого второй раз пул опустел бы молча.",
        "operationId": "upload_promo_codes_api_v1_ext_promo_pools__pool_id__codes_post",
        "parameters": [
          {
            "name": "pool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Pool Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PromoCodesUploadIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoCodesUploadOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:write"
        ]
      }
    },
    "/promo-pools/{pool_id}/codes/revoke": {
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Отозвать коды (вывести из оборота)",
        "description": "Погасить коды, которые больше не действуют на вашей стороне.\n\nОтменили партию, закрылась акция, код скомпрометирован — до сих пор мы\nумели только добавлять, и пул продолжал раздавать то, что у вас уже\nмертво. Клиент получал код от агента и упирался в «промокод неактивен».\n\nЧто делает отзыв:\n\n* **невыданный** код уходит из очереди — его больше никто не получит;\n* **выданный** перестаёт считаться «кодом этого клиента»: при следующем\n  обращении агент выдаст ему замену вместо мёртвого. Раньше выдача была\n  идемпотентна и возвращала тот же самый код.\n\nСтроки не удаляются: это история выдачи, на неё ссылается разбор жалоб, а\nкод у клиента на руках никуда не денется. Повтор безопасен — уже\nотозванные попадут в ``already_revoked``, а не отзовутся дважды.\n\nОбратная операция — просто залить эти коды снова: невыданный отозванный\nвернётся в оборот.",
        "operationId": "revoke_promo_codes_api_v1_ext_promo_pools__pool_id__codes_revoke_post",
        "parameters": [
          {
            "name": "pool_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Pool Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PromoCodesRevokeIn"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoCodesRevokeOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "promo:write"
        ]
      }
    },
    "/whatsapp/templates": {
      "get": {
        "tags": [
          "External API"
        ],
        "summary": "Реестр WhatsApp-шаблонов",
        "operationId": "list_wa_templates_api_v1_ext_whatsapp_templates_get",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Agent Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsAppTemplatesOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "External API"
        ],
        "summary": "Создать WhatsApp-шаблон",
        "operationId": "create_wa_template_api_v1_ext_whatsapp_templates_post",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Agent Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WhatsAppTemplateCreateIn"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "title": "Response Create Wa Template Api V1 Ext Whatsapp Templates Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "x-required-scopes": [
          "templates:manage"
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AgentOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "is_active": {
            "type": "boolean",
            "title": "Is Active"
          },
          "messengers": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Messengers"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "is_active"
        ],
        "title": "AgentOut"
      },
      "ApiWarningOut": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Machine-readable: avatar_unavailable, avatar_window_closed, …"
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "What happened, in plain words"
          }
        },
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "title": "ApiWarningOut",
        "description": "Something went sideways WITHOUT failing the request.\n\nDistinct from the error envelope on purpose: an error means nothing\nhappened, a warning means the main thing happened and a secondary one did\nnot. Registering a customer must not fail because their photo could not be\nfetched — but the caller still deserves to know why it is missing, in words,\ninstead of wondering at a null."
      },
      "ConversationCreateIn": {
        "properties": {
          "agent_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent Id",
            "description": "The agent (channel) the dialog belongs to. Omit only when the customer already has a dialog or a deal to inherit the agent from; in a company with several agents, pass it explicitly."
          }
        },
        "type": "object",
        "title": "ConversationCreateIn",
        "description": "``POST /ext/customers/{id}/conversations`` body.\n\n``agent_id`` picks the agent (Channel) the dialog belongs to. Omit it only\nwhen the customer already has a conversation or a deal to inherit the agent\nfrom — in a multi-agent company an omitted agent is a guess, so pass it."
      },
      "ConversationCreateOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id",
            "description": "Conversation id"
          },
          "channel_id": {
            "type": "string",
            "format": "uuid",
            "title": "Channel Id",
            "description": "The agent the dialog was opened on"
          },
          "crm_url": {
            "type": "string",
            "title": "Crm Url",
            "description": "Deep link straight to this dialog in the CRM — hand it to an operator and they land in the chat. Returned rather than left to you to assemble so the host is not hard-coded on your side: it differs between environments and has moved once already."
          },
          "customer_blocked": {
            "type": "boolean",
            "title": "Customer Blocked",
            "description": "The customer has blocked the bot on this agent's messenger, so nothing sent will reach them until they unblock it. SynQ knows this on its own — Telegram reports it and we keep the state; you send nothing about it. A machine-readable twin of the `customer_blocked_bot` warning, so you can branch on a boolean instead of scanning text.",
            "default": false
          },
          "warnings": {
            "items": {
              "$ref": "#/components/schemas/ApiWarningOut"
            },
            "type": "array",
            "title": "Warnings",
            "description": "Non-fatal problems. Today the one that matters is `customer_blocked_bot`: the dialog exists but nothing you send will reach them until they unblock."
          },
          "created": {
            "type": "boolean",
            "title": "Created",
            "description": "true — the dialog was created (HTTP 201); false — an active dialog for this pair already existed and is returned untouched (HTTP 200)."
          }
        },
        "type": "object",
        "required": [
          "id",
          "channel_id",
          "crm_url",
          "created"
        ],
        "title": "ConversationCreateOut"
      },
      "CustomerMergeIn": {
        "properties": {
          "from_customer_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "From Customer Id",
            "description": "Карточка-донор — она будет слита в основную"
          },
          "promo_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ],
            "title": "Promo Code",
            "description": "Донор = клиент, которому выдан этот код (альтернатива id)"
          },
          "keep_conversation_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Keep Conversation Id",
            "description": "Какой диалог остаётся активным на своём агенте. По умолчанию — диалог основной карточки: его id уже сохранён во внешнем сервисе"
          },
          "absorb_conversations": {
            "type": "boolean",
            "title": "Absorb Conversations",
            "description": "Склеить переписку в ОДНУ ленту: сообщения диалогов донора переезжают в целевой диалог, опустевшие уходят в архив. Источник каждого сообщения остаётся виден иконкой мессенджера",
            "default": false
          }
        },
        "type": "object",
        "title": "CustomerMergeIn",
        "description": "Кого сливаем в текущую карточку.\n\nДонор задаётся либо прямо (``from_customer_id``), либо промокодом\n(``promo_code``) — внешнему сервису известен код, а не наш id, и лишний\nround-trip за «кому он выдан» здесь ничего не добавляет."
      },
      "CustomerMergeOut": {
        "properties": {
          "winner_id": {
            "type": "string",
            "format": "uuid",
            "title": "Winner Id"
          },
          "donor_id": {
            "type": "string",
            "format": "uuid",
            "title": "Donor Id"
          },
          "already_merged": {
            "type": "boolean",
            "title": "Already Merged",
            "default": false
          },
          "messengers_moved": {
            "type": "integer",
            "title": "Messengers Moved",
            "default": 0
          },
          "conversations_moved": {
            "type": "integer",
            "title": "Conversations Moved",
            "default": 0
          },
          "conversations_archived": {
            "type": "integer",
            "title": "Conversations Archived",
            "default": 0
          },
          "conversations_dropped": {
            "type": "integer",
            "title": "Conversations Dropped",
            "default": 0
          },
          "messages_moved": {
            "type": "integer",
            "title": "Messages Moved",
            "default": 0
          },
          "deals_moved": {
            "type": "integer",
            "title": "Deals Moved",
            "default": 0
          },
          "deals_closed_as_duplicate": {
            "type": "integer",
            "title": "Deals Closed As Duplicate",
            "default": 0
          },
          "variables_moved": {
            "type": "integer",
            "title": "Variables Moved",
            "default": 0
          },
          "promo_codes_moved": {
            "type": "integer",
            "title": "Promo Codes Moved",
            "default": 0
          },
          "notes_moved": {
            "type": "integer",
            "title": "Notes Moved",
            "default": 0
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Warnings"
          }
        },
        "type": "object",
        "required": [
          "winner_id",
          "donor_id"
        ],
        "title": "CustomerMergeOut",
        "description": "Отчёт слияния — построчно.\n\nПеренос упирается в уникальные индексы (второй код из того же пула, вторая\nактивная сделка в одной воронке), и что именно НЕ переехало, вызывающий\nобязан увидеть: молчаливое «ок» здесь означало бы потерю данных на глазах."
      },
      "CustomerPlacement": {
        "properties": {
          "agent_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent Id"
          },
          "pipeline_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pipeline Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          }
        },
        "type": "object",
        "title": "CustomerPlacement",
        "description": "Where to drop the customer's deal in the funnel (overrides the default)."
      },
      "DealCloseIn": {
        "properties": {
          "status": {
            "type": "string",
            "pattern": "^(won|lost)$",
            "title": "Status",
            "description": "won | lost"
          },
          "reason": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          }
        },
        "type": "object",
        "required": [
          "status"
        ],
        "title": "DealCloseIn"
      },
      "DealCreateIn": {
        "properties": {
          "pipeline_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pipeline Id"
          },
          "stage_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          },
          "agent_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent Id"
          },
          "title": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ],
            "title": "Title"
          }
        },
        "type": "object",
        "title": "DealCreateIn",
        "description": "Create a deal with explicit placement (mirror of customer placement)."
      },
      "DealOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "pipeline_id": {
            "type": "string",
            "format": "uuid",
            "title": "Pipeline Id"
          },
          "stage_id": {
            "type": "string",
            "format": "uuid",
            "title": "Stage Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          }
        },
        "type": "object",
        "required": [
          "id",
          "pipeline_id",
          "stage_id"
        ],
        "title": "DealOut"
      },
      "DealsListOut": {
        "properties": {
          "deals": {
            "items": {
              "$ref": "#/components/schemas/ExtDealOut"
            },
            "type": "array",
            "title": "Deals"
          }
        },
        "type": "object",
        "title": "DealsListOut"
      },
      "ExtCustomerCreate": {
        "properties": {
          "full_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ],
            "title": "Full Name"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "email": {
            "anyOf": [
              {
                "type": "string",
                "format": "email"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "messengers": {
            "items": {
              "$ref": "#/components/schemas/MessengerIn"
            },
            "type": "array",
            "title": "Messengers"
          },
          "source": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ],
            "title": "Source"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Tags"
          },
          "variables": {
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "items": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "integer"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "type": "array"
                },
                {
                  "$ref": "#/components/schemas/ExtVariableIn"
                },
                {
                  "type": "null"
                }
              ]
            },
            "type": "object",
            "title": "Variables",
            "description": "Переменные клиента — тот же контракт, что у PATCH .../variables"
          },
          "placement": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CustomerPlacement"
              },
              {
                "type": "null"
              }
            ]
          },
          "language": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 8
              },
              {
                "type": "null"
              }
            ],
            "title": "Language",
            "description": "Preferred language, ISO 639-1"
          },
          "avatar_url": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Url",
            "description": "http(s) URL of the customer's photo. SynQ DOWNLOADS it once and serves its own copy — the link may expire afterwards, and the operator's browser never talks to your host. The response returns the stored path, not the URL you sent. A photo that cannot be fetched is skipped: the customer is still created, just without an avatar."
          },
          "fetch_avatar": {
            "type": "boolean",
            "title": "Fetch Avatar",
            "description": "Let SynQ pull the photo from the messenger itself, using the agent's own bot credentials. Nothing to host and no bytes to move: you already told us who the customer is. Works for a customer who has started the bot; if they have no photo or privacy hides it, the customer is created without one. Call it again later to refresh — an unchanged photo is not re-downloaded. Telegram, MAX and WhatsApp-personal support this; other messengers ignore it. Mutually exclusive with avatar_url.",
            "default": false
          }
        },
        "type": "object",
        "title": "ExtCustomerCreate",
        "description": "``POST /ext/customers`` body."
      },
      "ExtCustomerCreateOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "created": {
            "type": "boolean",
            "title": "Created"
          },
          "full_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Full Name"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "avatar_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Url"
          },
          "messengers": {
            "items": {
              "$ref": "#/components/schemas/MessengerOut"
            },
            "type": "array",
            "title": "Messengers"
          },
          "deal": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DealOut"
              },
              {
                "type": "null"
              }
            ]
          },
          "warnings": {
            "items": {
              "$ref": "#/components/schemas/ApiWarningOut"
            },
            "type": "array",
            "title": "Warnings",
            "description": "Non-fatal problems. Empty on a fully clean call."
          }
        },
        "type": "object",
        "required": [
          "id",
          "created"
        ],
        "title": "ExtCustomerCreateOut",
        "description": "``POST /ext/customers`` response (201 created / 200 upsert)."
      },
      "ExtCustomerGetOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "full_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Full Name"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "avatar_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Url"
          },
          "current_stage": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Current Stage"
          },
          "messengers": {
            "items": {
              "$ref": "#/components/schemas/MessengerOut"
            },
            "type": "array",
            "title": "Messengers"
          },
          "deal": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DealOut"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "id"
        ],
        "title": "ExtCustomerGetOut",
        "description": "``GET /ext/customers/{id}`` response."
      },
      "ExtDealOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "pipeline_id": {
            "type": "string",
            "format": "uuid",
            "title": "Pipeline Id"
          },
          "stage_id": {
            "type": "string",
            "format": "uuid",
            "title": "Stage Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          },
          "title": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Title"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "closed_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Closed Reason"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          },
          "moved_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moved At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "pipeline_id",
          "stage_id",
          "status"
        ],
        "title": "ExtDealOut",
        "description": "A deal as seen by the external API — any status, full lifecycle view."
      },
      "ExtVariableIn": {
        "properties": {
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "items": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Value",
            "description": "Значение. Список → множественная переменная. null → очистить значение, поле остаётся (удаляет поле короткая форма: null)"
          },
          "type": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "text",
                  "select",
                  "date",
                  "time",
                  "datetime",
                  "number",
                  "phone",
                  "email"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Type",
            "description": "Тип значения. Не указан — сохраняется прежний, для новой переменной выводится из JSON-типа значения"
          },
          "mode": {
            "type": "string",
            "enum": [
              "replace",
              "append",
              "remove"
            ],
            "title": "Mode",
            "description": "replace | append | remove (append/remove — для списков)",
            "default": "replace"
          }
        },
        "type": "object",
        "title": "ExtVariableIn",
        "description": "Расширенная форма записи переменной.\n\nНужна там, где короткой не хватает: объявить тип (чтобы «12» стало числом,\nа не строкой, и в конструкторе аудитории появились «больше/меньше»),\nдописать элемент в список, либо очистить значение НЕ удаляя поле."
      },
      "FollowUpIn": {
        "properties": {
          "after_minutes": {
            "type": "integer",
            "maximum": 10080,
            "minimum": 5,
            "title": "After Minutes",
            "description": "Delay in minutes (5 minutes … 7 days)"
          },
          "text": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 10000,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Text"
          },
          "template": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TemplateSendIn"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "after_minutes"
        ],
        "title": "FollowUpIn",
        "description": "A deferred follow-up in the same request: «send now, and nudge in N\nminutes if the customer stays silent». ANY inbound message from the customer\nafter the send cancels the reminder (checked when it fires). Free-form text\nis NOT sent when the 24h window (WA/IG/FB) is closed at firing time — use a\ntemplate for guaranteed delivery outside the window.\n\nDocstrings on the ext schemas are English: they are published verbatim in the\nOpenAPI slice behind /docs/api (see backend/scripts/export_ext_openapi.py)."
      },
      "FollowUpOut": {
        "properties": {
          "job_id": {
            "type": "string",
            "title": "Job Id"
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time",
            "title": "Scheduled At"
          }
        },
        "type": "object",
        "required": [
          "job_id",
          "scheduled_at"
        ],
        "title": "FollowUpOut"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "MessageSendIn": {
        "properties": {
          "messenger_type": {
            "type": "string",
            "maxLength": 50,
            "minLength": 1,
            "title": "Messenger Type"
          },
          "agent_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent Id"
          },
          "text": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 10000,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Text"
          },
          "template": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TemplateSendIn"
              },
              {
                "type": "null"
              }
            ]
          },
          "follow_up": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FollowUpIn"
              },
              {
                "type": "null"
              }
            ]
          },
          "pause_agent": {
            "type": "boolean",
            "title": "Pause Agent",
            "description": "Stop the AI agent in this dialog after the message goes out. Default false, which is what a notification wants: you tell the customer something and the agent keeps handling whatever they reply. Set true when a HUMAN is taking the conversation over through your own panel — the same effect as the pause button in the CRM, including the pause card in the timeline. Note this does NOT consult the agent's auto_pause_on_human setting: that one is about a human replying inside the CRM, and an API call cannot tell a notification from a person typing, so it asks you instead of guessing.",
            "default": false
          }
        },
        "type": "object",
        "required": [
          "messenger_type"
        ],
        "title": "MessageSendIn",
        "description": "``POST /ext/customers/{id}/messages`` body — exactly one of text/template."
      },
      "MessageSendOut": {
        "properties": {
          "message_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message Id"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "messenger_type": {
            "type": "string",
            "title": "Messenger Type"
          },
          "follow_up": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FollowUpOut"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "status",
          "messenger_type"
        ],
        "title": "MessageSendOut"
      },
      "MessengerIn": {
        "properties": {
          "type": {
            "type": "string",
            "maxLength": 50,
            "minLength": 1,
            "title": "Type",
            "description": "telegram, whatsapp, instagram, ..."
          },
          "id": {
            "type": "string",
            "maxLength": 255,
            "minLength": 1,
            "title": "Id",
            "description": "User id inside the messenger"
          },
          "username": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "type": "object",
        "required": [
          "type",
          "id"
        ],
        "title": "MessengerIn",
        "description": "A messenger identity supplied by the caller."
      },
      "MessengerOut": {
        "properties": {
          "type": {
            "type": "string",
            "title": "Type"
          },
          "id": {
            "type": "string",
            "title": "Id"
          },
          "username": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "type": "object",
        "required": [
          "type",
          "id"
        ],
        "title": "MessengerOut"
      },
      "MessengerWindowOut": {
        "properties": {
          "type": {
            "type": "string",
            "title": "Type"
          },
          "id": {
            "type": "string",
            "title": "Id"
          },
          "username": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          },
          "last_inbound_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Inbound At"
          },
          "can_send_freeform": {
            "type": "boolean",
            "title": "Can Send Freeform"
          },
          "window_expires_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Window Expires At"
          }
        },
        "type": "object",
        "required": [
          "type",
          "id",
          "can_send_freeform"
        ],
        "title": "MessengerWindowOut",
        "description": "A messenger identity with the computed send-window state (§9)."
      },
      "MessengersListOut": {
        "properties": {
          "messengers": {
            "items": {
              "$ref": "#/components/schemas/MessengerWindowOut"
            },
            "type": "array",
            "title": "Messengers"
          }
        },
        "type": "object",
        "required": [
          "messengers"
        ],
        "title": "MessengersListOut"
      },
      "PipelineOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "agent_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent Id"
          },
          "is_default": {
            "type": "boolean",
            "title": "Is Default"
          },
          "stages": {
            "items": {
              "$ref": "#/components/schemas/StageOut"
            },
            "type": "array",
            "title": "Stages"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "is_default"
        ],
        "title": "PipelineOut"
      },
      "PromoCodeLookupOut": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "pool_id": {
            "type": "string",
            "format": "uuid",
            "title": "Pool Id"
          },
          "pool_title": {
            "type": "string",
            "title": "Pool Title"
          },
          "customer_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Id"
          },
          "customer_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Name"
          },
          "messengers": {
            "items": {
              "$ref": "#/components/schemas/MessengerOut"
            },
            "type": "array",
            "title": "Messengers"
          },
          "reserved_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reserved At"
          },
          "issued_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Issued At"
          },
          "revoked_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Revoked At"
          }
        },
        "type": "object",
        "required": [
          "code",
          "pool_id",
          "pool_title"
        ],
        "title": "PromoCodeLookupOut",
        "description": "«Кому выдан этот код».\n\nВнешний сервис опознаёт человека по своему идентификатору (Telegram), а код\nмог быть выдан в другом мессенджере. Эта ручка — мост: по коду возвращается\nклиент SynQ вместе со всеми его мессенджерами, и вызывающий сам решает, что\nделать дальше — писать переменную или сливать карточки."
      },
      "PromoCodeOut": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "position": {
            "type": "integer",
            "title": "Position"
          },
          "customer_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Id"
          },
          "reserved_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reserved At"
          },
          "issued_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Issued At"
          },
          "revoked_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Revoked At"
          }
        },
        "type": "object",
        "required": [
          "code",
          "position"
        ],
        "title": "PromoCodeOut",
        "description": "Строка истории выдач."
      },
      "PromoCodesListOut": {
        "properties": {
          "codes": {
            "items": {
              "$ref": "#/components/schemas/PromoCodeOut"
            },
            "type": "array",
            "title": "Codes"
          }
        },
        "type": "object",
        "title": "PromoCodesListOut"
      },
      "PromoCodesRevokeIn": {
        "properties": {
          "codes": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "minItems": 1,
            "title": "Codes",
            "description": "Коды, которые больше не действуют"
          }
        },
        "type": "object",
        "required": [
          "codes"
        ],
        "title": "PromoCodesRevokeIn",
        "description": "Вывести коды из оборота по их значению."
      },
      "PromoCodesRevokeOut": {
        "properties": {
          "revoked": {
            "type": "integer",
            "title": "Revoked"
          },
          "issued_revoked": {
            "type": "integer",
            "title": "Issued Revoked"
          },
          "already_revoked": {
            "type": "integer",
            "title": "Already Revoked"
          },
          "not_found": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Not Found"
          },
          "total": {
            "type": "integer",
            "title": "Total"
          },
          "remaining": {
            "type": "integer",
            "title": "Remaining"
          }
        },
        "type": "object",
        "required": [
          "revoked",
          "issued_revoked",
          "already_revoked",
          "total",
          "remaining"
        ],
        "title": "PromoCodesRevokeOut",
        "description": "Что стало с каждым присланным кодом.\n\nРазбивка целиком: «отозвано 3 из 10» без ответа, куда делись остальные\nсемь, оставляет вызывающего гадать — опечатка в кодах или они уже были\nпогашены раньше."
      },
      "PromoCodesUploadIn": {
        "properties": {
          "codes": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "minItems": 1,
            "title": "Codes",
            "description": "Коды, по одному в элементе списка"
          }
        },
        "type": "object",
        "required": [
          "codes"
        ],
        "title": "PromoCodesUploadIn",
        "description": "Долив кодов. Повторная заливка того же файла безопасна: дедуп по\n``(pool_id, code)`` живёт в уникальном индексе, а не в проверке кода."
      },
      "PromoCodesUploadOut": {
        "properties": {
          "accepted": {
            "type": "integer",
            "title": "Accepted"
          },
          "duplicates_in_input": {
            "type": "integer",
            "title": "Duplicates In Input"
          },
          "already_in_pool": {
            "type": "integer",
            "title": "Already In Pool"
          },
          "total": {
            "type": "integer",
            "title": "Total"
          },
          "remaining": {
            "type": "integer",
            "title": "Remaining"
          }
        },
        "type": "object",
        "required": [
          "accepted",
          "duplicates_in_input",
          "already_in_pool",
          "total",
          "remaining"
        ],
        "title": "PromoCodesUploadOut",
        "description": "Отчёт заливки — целиком.\n\nБез разбивки «принято / дубли во входе / уже были в пуле» вызывающий,\nзалив файл дважды, уверен, что кодов вдвое больше, чем есть."
      },
      "PromoPoolOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "agent_id": {
            "type": "string",
            "format": "uuid",
            "title": "Agent Id",
            "description": "Агент-владелец пула"
          },
          "title": {
            "type": "string",
            "title": "Title"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "is_enabled": {
            "type": "boolean",
            "title": "Is Enabled"
          },
          "total": {
            "type": "integer",
            "title": "Total",
            "description": "Всего кодов, включая выданные"
          },
          "issued": {
            "type": "integer",
            "title": "Issued",
            "description": "Закреплено за клиентами"
          },
          "remaining": {
            "type": "integer",
            "title": "Remaining",
            "description": "Свободно к выдаче"
          },
          "low_threshold": {
            "type": "integer",
            "title": "Low Threshold",
            "description": "Остаток, ниже которого уходит алерт"
          },
          "low_alert_sent_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Low Alert Sent At"
          },
          "empty_alert_sent_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Empty Alert Sent At"
          },
          "exhausted_requests": {
            "type": "integer",
            "title": "Exhausted Requests",
            "default": 0
          },
          "code_word_titles": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Code Word Titles"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "agent_id",
          "title",
          "is_enabled",
          "total",
          "issued",
          "remaining",
          "low_threshold"
        ],
        "title": "PromoPoolOut",
        "description": "Пул промокодов глазами внешнего сервиса.\n\nГлавное поле — ``remaining``: ради ответа «когда пополнять» эти ручки и\nсуществуют. Порог отдаём посчитанным (``low_threshold``), чтобы у\nвызывающего и у нашего алерта была одна и та же цифра — иначе внешний\nмонитор начнёт кричать не тогда же, когда система."
      },
      "PromoPoolUpdateIn": {
        "properties": {
          "is_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Is Enabled",
            "description": "Выключенный пул не выдаёт коды вообще — самый быстрый способ остановить раздачу, не трогая сами коды"
          },
          "title": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 128,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Title"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "low_threshold_pct": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 100,
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "title": "Low Threshold Pct"
          },
          "low_threshold_min": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "title": "Low Threshold Min"
          },
          "strict_follow_gate": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Strict Follow Gate"
          },
          "exhausted_instruction": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Exhausted Instruction",
            "description": "Что агенту предлагать клиенту, когда коды кончились. Инструкция по смыслу, а не готовый текст — уходит на языке клиента"
          }
        },
        "type": "object",
        "title": "PromoPoolUpdateIn",
        "description": "Настройки пула, которые вправе менять внешний сервис.\n\nСчётчики сюда не входят — они факт, а не настройка."
      },
      "PromoPoolsListOut": {
        "properties": {
          "pools": {
            "items": {
              "$ref": "#/components/schemas/PromoPoolOut"
            },
            "type": "array",
            "title": "Pools"
          }
        },
        "type": "object",
        "title": "PromoPoolsListOut"
      },
      "StageChangeIn": {
        "properties": {
          "stage_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          },
          "reason": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          }
        },
        "type": "object",
        "title": "StageChangeIn",
        "description": "``POST /ext/customers/{id}/stage`` body — exactly one target."
      },
      "StageChangeOut": {
        "properties": {
          "deal_id": {
            "type": "string",
            "format": "uuid",
            "title": "Deal Id"
          },
          "stage_id": {
            "type": "string",
            "format": "uuid",
            "title": "Stage Id"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          },
          "moved_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Moved At"
          }
        },
        "type": "object",
        "required": [
          "deal_id",
          "stage_id"
        ],
        "title": "StageChangeOut"
      },
      "StageOut": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "stage_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stage Code"
          },
          "position": {
            "type": "integer",
            "title": "Position"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "position"
        ],
        "title": "StageOut"
      },
      "TemplateSendIn": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 512,
            "minLength": 1,
            "title": "Name"
          },
          "lang": {
            "type": "string",
            "maxLength": 15,
            "minLength": 2,
            "title": "Lang"
          },
          "variables": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Variables"
          },
          "button_param": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Button Param"
          }
        },
        "type": "object",
        "required": [
          "name",
          "lang"
        ],
        "title": "TemplateSendIn"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "VariableOut": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name"
          },
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Value"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "select",
              "datetime",
              "number",
              "date",
              "time",
              "phone",
              "email"
            ],
            "title": "Type"
          },
          "is_list": {
            "type": "boolean",
            "title": "Is List",
            "default": false
          },
          "value_list": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Value List"
          },
          "group_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Group Name"
          },
          "updated_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Updated At"
          }
        },
        "type": "object",
        "required": [
          "name",
          "value",
          "type"
        ],
        "title": "VariableOut",
        "description": "Одна переменная в ответе.\n\nПлоского ``{имя: значение}`` не хватает: у значения есть тип, от которого\nзависит, что с ним можно делать, и множественная форма. ``value`` остаётся\nзаполненным и у списка (читаемая склейка), чтобы простому потребителю\nпо-прежнему хватало одного поля."
      },
      "VariablesOut": {
        "properties": {
          "variables": {
            "items": {
              "$ref": "#/components/schemas/VariableOut"
            },
            "type": "array",
            "title": "Variables"
          }
        },
        "type": "object",
        "required": [
          "variables"
        ],
        "title": "VariablesOut"
      },
      "VariablesUpdateIn": {
        "properties": {
          "variables": {
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "items": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "integer"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "type": "array"
                },
                {
                  "$ref": "#/components/schemas/ExtVariableIn"
                },
                {
                  "type": "null"
                }
              ]
            },
            "type": "object",
            "title": "Variables",
            "description": "Имя → значение (короткая форма) либо объект {value, type, mode}. Значение null удаляет переменную"
          }
        },
        "type": "object",
        "required": [
          "variables"
        ],
        "title": "VariablesUpdateIn",
        "description": "``PATCH .../variables`` — точечный upsert по ИМЕНИ переменной.\n\nПередавать можно короткой формой (``{\"плата\": 12}``) или расширенной\n(``{\"плата\": {\"value\": 12, \"type\": \"number\"}}``) — вперемешку. Не\nупомянутые переменные не трогаются: это patch, а не замена набора."
      },
      "WhatsAppTemplateCreateIn": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 512,
            "minLength": 1,
            "title": "Name"
          },
          "category": {
            "type": "string",
            "title": "Category",
            "description": "MARKETING | UTILITY | AUTHENTICATION"
          },
          "language": {
            "type": "string",
            "maxLength": 15,
            "minLength": 2,
            "title": "Language"
          },
          "components": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Components",
            "description": "Meta component array (header/body/footer/buttons)"
          }
        },
        "type": "object",
        "required": [
          "name",
          "category",
          "language",
          "components"
        ],
        "title": "WhatsAppTemplateCreateIn",
        "description": "``POST /ext/whatsapp/templates`` body — proxied to Meta as-is."
      },
      "WhatsAppTemplateOut": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name"
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status"
          },
          "category": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Category"
          },
          "language": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Language"
          },
          "variables": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Variables"
          },
          "has_buttons": {
            "type": "boolean",
            "title": "Has Buttons",
            "default": false
          }
        },
        "type": "object",
        "required": [
          "name"
        ],
        "title": "WhatsAppTemplateOut"
      },
      "WhatsAppTemplatesOut": {
        "properties": {
          "templates": {
            "items": {
              "$ref": "#/components/schemas/WhatsAppTemplateOut"
            },
            "type": "array",
            "title": "Templates"
          }
        },
        "type": "object",
        "required": [
          "templates"
        ],
        "title": "WhatsAppTemplatesOut"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Company API key: `Authorization: Bearer sk_live_...`"
      }
    }
  }
}
