{"openapi":"3.0.0","paths":{"/api/v1/validate/single":{"post":{"description":"Отправляет один email-адрес на валидацию. Система проверяет существование почтового ящика через DNS/MX-записи, SMTP-подключение и провайдер-специфичные методы.\n\n**Как это работает:**\n1. Email проходит синтаксическую проверку. Если формат невалидный — возвращается мгновенный ответ с `status: \"invalid\"` без списания кредитов.\n2. Если формат корректный — email ставится в очередь на проверку, списывается 1 кредит, возвращается `task_id`.\n3. Проверка занимает от нескольких секунд до 2 минут в зависимости от домена.\n\n**Получение результата:**\n- **Polling:** используйте `GET /api/v1/tasks/:taskId` для отслеживания статуса, затем `GET /api/v1/tasks/:taskId/results` для получения результата.\n- **Webhook:** передайте `webhook_url` в запросе — мы отправим POST-запрос на указанный URL, когда проверка завершится.\n\n**Кредиты:** 1 email = 1 кредит. Кредит списывается сразу при постановке в очередь. Невалидные по синтаксису email не тарифицируются.","operationId":"ValidationController_validateSingle","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SingleValidationDto"}}}},"responses":{"200":{"description":"Email поставлен в очередь на валидацию. Используйте `task_id` из ответа для получения результата.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SingleValidationQueuedResponse"}}}},"401":{"description":"Отсутствует или неверный API ключ / JWT токен. Проверьте заголовок `x-api-key` или `Authorization`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}},"403":{"description":"На балансе недостаточно кредитов. Пополните баланс в личном кабинете.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForbiddenResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Проверить один email-адрес","tags":["Валидация Email"]}},"/api/v1/validate/bulk":{"post":{"description":"Отправляет массив email-адресов на пакетную валидацию. Оптимальный способ проверки больших списков через API.\n\n**Обработка невалидных адресов:**\nПеред постановкой в очередь каждый email проходит синтаксическую проверку. Адреса с невалидным форматом автоматически исключаются из задачи, **не тарифицируются** и возвращаются в поле `invalid_details` ответа. Вы платите только за реально проверяемые email.\n\n**Идемпотентность:**\nПередайте уникальный `idempotency_key` для защиты от дублирования задач при повторных запросах (например, при сетевых таймаутах). Повторный запрос с тем же ключом вернёт существующую задачу вместо создания новой.\n\n**Webhook-уведомления:**\nУкажите `webhook_url` — мы отправим POST-запрос с результатами, когда задача завершится. Это избавляет от необходимости polling.\n\n**Кредиты:**\nСписываются только за email с валидным синтаксисом. Формула: `credits_used = valid_emails`. Перед отправкой проверьте баланс через `GET /api/v1/account/balance`.\n\n**Лимиты:**\nМаксимальный размер одного запроса ограничен 100 MB. Для очень больших списков рекомендуем разбивать на пакеты по 50 000–100 000 email.","operationId":"ValidationController_validateBulk","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkValidationDto"}}}},"responses":{"200":{"description":"Задача создана. Ответ содержит `task_id`, количество принятых и отклонённых email, списанные кредиты.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkValidationResponse"}}}},"401":{"description":"Отсутствует или неверный API ключ / JWT токен.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}},"403":{"description":"Недостаточно кредитов для указанного количества email. Пополните баланс.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForbiddenResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Массовая проверка email-адресов","tags":["Валидация Email"]}},"/api/v1/tasks/{taskId}":{"get":{"description":"Возвращает текущее состояние задачи валидации, включая прогресс обработки в процентах.\n\n**Когда использовать:**\nВызывайте этот эндпоинт для отслеживания прогресса задачи перед получением результатов. Результаты доступны только для задач в статусе `completed`.\n\n**Рекомендуемый polling-паттерн:**\n1. Отправьте запрос на валидацию (`POST /api/v1/validate/single` или `/bulk`).\n2. Запрашивайте статус каждые 5–10 секунд.\n3. Когда `status` станет `completed` — вызовите `GET /api/v1/tasks/:taskId/results`.\n\n**Состояния задачи:**\n- `pending` — задача в очереди, обработка не начата\n- `processing` — идёт проверка, поле `progress_percent` показывает прогресс (0–100)\n- `completed` — все email проверены, результаты доступны\n- `failed` — произошла ошибка (обратитесь в поддержку с `task_id`)\n\n**Безопасность:** задача доступна только владельцу аккаунта, создавшему её.","operationId":"ValidationController_getTask","parameters":[{"name":"taskId","required":true,"in":"path","description":"Числовой идентификатор задачи, полученный при создании через `POST /api/v1/validate/single` или `/bulk`","schema":{"example":123,"type":"number"}}],"responses":{"200":{"description":"Текущий статус задачи с информацией о прогрессе.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskStatusResponse"}}}},"404":{"description":"Задача не найдена или не принадлежит вашему аккаунту.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить статус и прогресс задачи","tags":["Валидация Email"]}},"/api/v1/tasks/{taskId}/results":{"get":{"description":"Возвращает результаты проверки email для завершённой задачи в формате JSON или CSV.\n\n**Важно:** результаты доступны только для задач в статусе `completed`. Для задач в других состояниях будет возвращена ошибка. Предварительно проверьте статус через `GET /api/v1/tasks/:taskId`.\n\n**Формат ответа:**\n- `json` (по умолчанию) — массив объектов с полями `email`, `validation_result`, `result`. Удобен для программной обработки.\n- `csv` — текстовый формат с заголовками `email,validation_result,result`. Удобен для импорта в Excel и другие инструменты.\n\n**Структура результата:**\n- `validation_result` — итоговый вердикт: `good` (email валиден) или `bad` (email невалиден).\n- `result` — детальная причина для невалидных адресов: `mailbox_not_found`, `domain_not_found`, `smtp_rejected` и др.\n\n**Совет:** для скачивания файла используйте специализированные эндпоинты `GET /api/v1/tasks/:taskId/results/csv` (CSV-файл) или `GET /api/v1/tasks/:taskId/download` (ZIP-архив с разделением на good/bad).","operationId":"ValidationController_getTaskResults","parameters":[{"name":"taskId","required":true,"in":"path","description":"Числовой идентификатор задачи","schema":{"example":123,"type":"number"}},{"name":"format","required":false,"in":"query","description":"Формат ответа. По умолчанию `json`. Формат `csv` возвращает данные как текстовую строку с заголовками.","schema":{"enum":["json","csv"],"type":"string"}}],"responses":{"200":{"description":"Результаты валидации в запрошенном формате.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskResultsJsonResponse"}}}},"404":{"description":"Задача не найдена или не принадлежит вашему аккаунту.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить результаты валидации","tags":["Валидация Email"]}},"/api/v1/tasks/{taskId}/results/csv":{"get":{"description":"Возвращает результаты валидации как скачиваемый CSV-файл. Браузер и HTTP-клиенты автоматически предложат сохранить файл.\n\n**Формат файла:**\n- Кодировка: UTF-8\n- Разделитель: запятая\n- Колонки: `email`, `validation_result`, `result`\n- Первая строка — заголовки\n\n**Пример содержимого:**\n```\nemail,validation_result,result\nuser@gmail.com,good,\nbad@nonexistent.xyz,bad,domain_not_found\n```\n\n**Когда использовать:**\nЕсли вам нужен единый файл со всеми результатами для импорта в Excel, Google Sheets или CRM. Для разделённых списков (отдельно валидные и невалидные) используйте `GET /api/v1/tasks/:taskId/download` — он возвращает ZIP-архив.\n\n**Важно:** результаты доступны только для задач в статусе `completed`.","operationId":"ValidationController_downloadCsv","parameters":[{"name":"taskId","required":true,"in":"path","description":"Числовой идентификатор задачи","schema":{"example":123,"type":"number"}}],"responses":{"200":{"description":"CSV-файл с результатами. Content-Type: `text/csv`, Content-Disposition: `attachment`."},"404":{"description":"Задача не найдена, не принадлежит вашему аккаунту, или результаты ещё не готовы.","content":{"text/csv":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Скачать результаты в формате CSV","tags":["Валидация Email"]}},"/api/v1/tasks/{taskId}/download":{"get":{"description":"Возвращает результаты валидации в виде ZIP-архива с двумя текстовыми файлами, готовыми для загрузки в ESP или CRM.\n\n**Содержимое архива:**\n- `good.txt` — валидные email-адреса (по одному на строку)\n- `bad.txt` — невалидные email-адреса (по одному на строку)\n\n**Когда использовать:**\nЭтот формат удобен, когда вам нужен чистый список адресов без дополнительных колонок — например, для загрузки в рассылочный сервис. Если нужен детальный отчёт с причинами отклонения, используйте CSV-формат: `GET /api/v1/tasks/:taskId/results/csv`.\n\n**Важно:** результаты доступны только для задач в статусе `completed`. Проверьте статус задачи через `GET /api/v1/tasks/:taskId` перед скачиванием.","operationId":"ValidationController_downloadTaskResults","parameters":[{"name":"taskId","required":true,"in":"path","description":"Числовой идентификатор задачи","schema":{"example":123,"type":"number"}}],"responses":{"200":{"description":"ZIP-архив с файлами `good.txt` и `bad.txt`. Content-Type: `application/zip`."},"404":{"description":"Задача не найдена, не принадлежит вашему аккаунту, или результаты ещё не готовы.","content":{"application/zip":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]},{"bearer":[]}],"summary":"Скачать результаты в ZIP-архиве","tags":["Валидация Email"]}},"/api/v1/tasks/{taskId}/analytics":{"get":{"description":"Возвращает агрегированную статистику по задаче валидации: счётчики результатов, доставляемость и разбивку невалидных адресов по категориям причин.\n\n**Что включено:**\n- `total` / `good` / `bad` / `unknown` — счётчики адресов по итоговому результату (`validation_result`).\n- `deliverability` — доля валидных адресов в процентах (`good / total * 100`), целое число 0–100.\n- `reasons` — разбивка невалидных (`bad`) адресов по категориям причин отклонения, отсортированная по убыванию количества.\n\n**Категории причин (`reasons[].key`):**\n`disposable` (одноразовые/временные домены), `catch_all`, `role` (ролевые адреса), `spam_trap` (спам-ловушки/blackhole), `syntax` (синтаксис), `no_mx` (нет MX/недоступны), `smtp_reject` (отказ почтового сервера). Нераспознанные значения возвращаются как slug исходной причины либо `other`.\n\n**Безопасность:** задача доступна только владельцу аккаунта, создавшему её.","operationId":"ValidationController_getTaskAnalytics","parameters":[{"name":"taskId","required":true,"in":"path","description":"Числовой идентификатор задачи","schema":{"example":123,"type":"number"}}],"responses":{"200":{"description":"Агрегированная аналитика по задаче.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskAnalyticsResponse"}}}},"404":{"description":"Задача не найдена или не принадлежит вашему аккаунту.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить аналитику по задаче","tags":["Валидация Email"]}},"/api/v1/tasks":{"get":{"description":"Возвращает постраничный список всех задач валидации для текущего аккаунта. Задачи отсортированы по дате создания — новые первыми.\n\n**Пагинация:**\nИспользуйте параметры `page` и `limit` для навигации по результатам. Ответ содержит поле `total` с общим числом задач — используйте его для расчёта количества страниц.\n\n**Что включено:**\nСписок содержит задачи во всех состояниях: `pending`, `processing`, `completed`, `failed`. Каждая задача включает `task_id`, имя файла, статус и временные метки создания/завершения.\n\n**Типичное использование:**\nОтображение истории проверок в личном кабинете или мониторинг активных задач через API.","operationId":"ValidationController_getTasks","parameters":[{"name":"page","required":false,"in":"query","description":"Номер страницы. Нумерация начинается с 1. По умолчанию: 1.","schema":{"example":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Количество задач на странице. Минимум: 1, максимум: 100. По умолчанию: 10.","schema":{"example":10,"type":"number"}}],"responses":{"200":{"description":"Постраничный список задач с метаданными пагинации.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TasksListResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить список всех задач","tags":["Валидация Email"]}},"/api/v1/account/balance":{"get":{"description":"Возвращает текущий баланс кредитов, маскированный API ключ и идентификатор аккаунта.\n\n**Когда использовать:**\n- Перед отправкой массовой валидации — убедитесь, что кредитов достаточно.\n- Для отображения баланса в интерфейсе вашего приложения.\n- Для мониторинга расхода кредитов.\n\n**Безопасность:**\nAPI ключ возвращается в маскированном виде (первые 8 символов + `...`). Полный ключ отображается только в личном кабинете и при сбросе через `POST /auth/reset-api-key`.","operationId":"ValidationController_getBalance","parameters":[],"responses":{"200":{"description":"Информация об аккаунте: баланс кредитов, маскированный API ключ, идентификатор.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountBalanceResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Проверить баланс и информацию об аккаунте","tags":["Валидация Email"]}},"/api/v1/account/stats":{"get":{"description":"Возвращает сводную статистику по аккаунту: количество задач, суммарное число проверенных адресов, среднюю доставляемость и разбивку по последней завершённой задаче.\n\n**Что включено:**\n- `tasks_count` — общее количество задач на аккаунте.\n- `emails_checked` — суммарное количество проверенных email-адресов по всем задачам.\n- `avg_deliverability` — средняя доставляемость (`good / total * 100`) по завершённым задачам, целое число 0–100. `0`, если завершённых задач нет.\n- `last_list` — разбивка по последней завершённой задаче (`total`/`good`/`bad`/`unknown`) или `null`, если завершённых задач нет.\n\n**Когда использовать:** для отображения сводных метрик аккаунта в личном кабинете.","operationId":"ValidationController_getStats","parameters":[],"responses":{"200":{"description":"Сводная статистика аккаунта.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStatsResponse"}}}},"401":{"description":"Отсутствует или неверный API ключ / JWT токен.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить статистику аккаунта","tags":["Валидация Email"]}},"/auth/login":{"post":{"description":"Аутентификация по email и паролю. При успешном входе возвращает набор данных для работы с API.\n\n**Что возвращается:**\n- `access_token` — JWT токен для аутентификации запросов (время жизни: **1 час**). Передавайте в заголовке `Authorization: Bearer <token>`.\n- `refresh_token` — токен для обновления access_token (время жизни: **7 дней**). Используйте `POST /auth/refresh`.\n- `api_key` — персональный API ключ для программного доступа. Не истекает.\n- `user` — информация об аккаунте.\n\n**Рекомендация:**\nДля серверных интеграций используйте API ключ (`x-api-key`) вместо JWT — это проще и не требует логики обновления токенов. JWT подходит для фронтенд-приложений, где нужна сессия с ограниченным временем жизни.","operationId":"AuthController_login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginDto"}}}},"responses":{"200":{"description":"Успешная аутентификация. Ответ содержит JWT токены, API ключ и данные аккаунта.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Неверный email или пароль.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}}},"summary":"Вход по email и паролю","tags":["Аутентификация"]}},"/auth/refresh":{"post":{"description":"Обменивает действующий refresh token на новую пару access + refresh токенов. Используется для продления сессии без повторного ввода пароля.\n\n**Ротация токенов:**\nПри каждом вызове старый refresh token **немедленно аннулируется**, и выдаётся новый. Это обеспечивает безопасность: если refresh token скомпрометирован, он может быть использован только один раз.\n\n**Когда вызывать:**\nВызывайте этот эндпоинт, когда access token истёк (ответ `401`) или заблаговременно — например, за 5 минут до истечения. Refresh token действует 7 дней.\n\n**Важно:** для вызова этого эндпоинта необходимо передать текущий access token в заголовке `Authorization`, даже если он просрочен — сервер проверяет его подпись, но не срок действия.","operationId":"AuthController_refresh","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenDto"}}}},"responses":{"200":{"description":"Новая пара токенов. Старый refresh token аннулирован.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenResponse"}}}},"401":{"description":"Refresh token недействителен, просрочен или уже был использован.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}}},"security":[{"bearer":[]}],"summary":"Обновить JWT токены","tags":["Аутентификация"]}},"/auth/telegram-login":{"post":{"description":"Аутентификация по Telegram chat ID. Предназначен для интеграции с Telegram-ботом UChecker.\n\n**Как это работает:**\n1. Пользователь взаимодействует с Telegram-ботом UChecker.\n2. Бот получает `chat_id` пользователя и вызывает этот эндпоинт.\n3. Если аккаунт с таким `chat_id` существует — возвращаются JWT токены и API ключ (аналогично `POST /auth/login`).\n\n**Требования:**\nАккаунт должен быть предварительно привязан к Telegram через `POST /auth/link-telegram` или создан через бота. Если аккаунт не найден — возвращается ошибка 400.\n\n**Область применения:** этот эндпоинт используется внутренним Telegram-ботом и не предназначен для прямого вызова из пользовательских приложений.","operationId":"AuthController_telegramLogin","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"telegram_id":{"type":"string","example":"123456789","description":"Telegram chat ID пользователя (числовой, передаётся как строка)"}},"required":["telegram_id"]}}}},"responses":{"200":{"description":"Успешная аутентификация. Ответ идентичен `POST /auth/login`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Аккаунт с указанным Telegram chat ID не найден. Необходима привязка."}},"summary":"Вход через Telegram","tags":["Аутентификация"]}},"/auth/forgot-password":{"post":{"description":"Инициирует процесс восстановления пароля. Если указанный email зарегистрирован, на него отправляется письмо со ссылкой для сброса (через тот же канал, что и подтверждение регистрации — Rusender).\n\n**Защита от перебора:**\nЭндпоинт всегда возвращает одно и то же сообщение, независимо от того, зарегистрирован email или нет. Это защищает от перечисления существующих аккаунтов.\n\n**Срок действия ссылки:** 60 минут с момента отправки письма. Повторный вызов выпускает новый токен — старая ссылка перестаёт работать.\n\n**Что происходит после сброса:**\nПосле успешной смены пароля (`POST /auth/reset-password`) все активные сессии аккаунта инвалидируются — refresh_token обнуляется. На всех устройствах потребуется повторный вход.","operationId":"AuthController_forgotPassword","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordDto"}}}},"responses":{"200":{"description":"Запрос принят. Если email зарегистрирован — письмо отправлено."}},"summary":"Запросить сброс пароля","tags":["Аутентификация"]}},"/auth/reset-password":{"post":{"description":"Завершает процесс сброса пароля. Принимает одноразовый токен из письма и новый пароль.\n\n**Поведение:**\n- Токен валидируется по сроку действия (60 минут). Просроченный или несуществующий токен — `400`.\n- При успехе пароль заменяется, токен обнуляется, все активные сессии завершаются (refresh_token = null).\n- Эндпоинт **не возвращает JWT** — пользователь должен войти с новым паролем через `POST /auth/login`.","operationId":"AuthController_resetPassword","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordDto"}}}},"responses":{"200":{"description":"Пароль успешно изменён."},"400":{"description":"Токен недействителен или истёк."}},"summary":"Установить новый пароль по токену из письма","tags":["Аутентификация"]}},"/auth/reset-api-key":{"post":{"description":"Генерирует новый API ключ и **немедленно аннулирует** предыдущий. Все запросы со старым ключом начнут возвращать `401 Unauthorized`.\n\n**Когда использовать:**\n- Подозрение на компрометацию ключа.\n- Ротация ключей в рамках политики безопасности.\n- Передача доступа другому разработчику.\n\n**Важные моменты:**\n- Требуется JWT аутентификация (`Authorization: Bearer <token>`). API ключ нельзя использовать для его собственного сброса.\n- Новый ключ возвращается в ответе **один раз**. Сохраните его — в дальнейшем он отображается только в маскированном виде.\n- Все активные интеграции, использующие старый ключ, перестанут работать. Обновите ключ во всех системах.","operationId":"AuthController_resetApiKey","parameters":[],"responses":{"200":{"description":"Новый API ключ сгенерирован. Старый ключ аннулирован.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetApiKeyResponse"}}}},"401":{"description":"JWT токен отсутствует, невалиден или просрочен.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}}},"security":[{"bearer":[]}],"summary":"Сбросить и перегенерировать API ключ","tags":["Аутентификация"]}},"/auth/link-telegram":{"post":{"description":"Начинает процесс привязки Telegram-аккаунта к веб-аккаунту UChecker. После привязки пользователь сможет входить через Telegram-бота и управлять задачами из мессенджера.\n\n**Пошаговый процесс привязки:**\n1. Пользователь пишет команду `/link` в Telegram-бот UChecker и получает 6-значный код.\n2. Пользователь вводит этот код в личном кабинете на сайте — приложение вызывает данный эндпоинт.\n3. Бот отправляет пользователю запрос на подтверждение в Telegram.\n4. Пользователь подтверждает в боте — привязка завершена.\n\n**Ошибки:**\n- Неверный или просроченный код — `400`.\n- Аккаунт уже привязан к другому Telegram — `400`.\n- Telegram-аккаунт уже привязан к другому веб-аккаунту — `400`.","operationId":"AuthController_initiateLinkTelegram","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateLinkDto"}}}},"responses":{"200":{"description":"Запрос на привязку создан. Ожидается подтверждение в Telegram-боте."},"400":{"description":"Неверный/просроченный код, аккаунт уже привязан, или Telegram ID уже используется."}},"security":[{"bearer":[]}],"summary":"Инициировать привязку Telegram аккаунта","tags":["Аутентификация"]}},"/auth/confirm-link":{"post":{"description":"Завершает процесс привязки Telegram-аккаунта. Вызывается Telegram-ботом, когда пользователь нажимает кнопку «Подтвердить» или «Отклонить».\n\n**Внутренний эндпоинт:**\nПредназначен для вызова Telegram-ботом, а не пользовательскими приложениями напрямую.\n\n**Параметры:**\n- `request_id` — идентификатор запроса на привязку (из callback_data кнопки).\n- `chat_id` — Telegram chat ID для дополнительной верификации.\n- `confirmed` — `true` для подтверждения, `false` для отклонения.\n\n**Поведение:**\nПри подтверждении (`confirmed: true`) Telegram chat ID привязывается к веб-аккаунту, и пользователь получает возможность входить через бота. При отклонении запрос аннулируется.","operationId":"AuthController_confirmLink","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmLinkDto"}}}},"responses":{"200":{"description":"Привязка подтверждена или отклонена."},"400":{"description":"Запрос на привязку не найден, истёк, или уже обработан."}},"summary":"Подтвердить или отклонить привязку Telegram (для бота)","tags":["Аутентификация"]}},"/auth/confirm-link-simple":{"post":{"description":"Упрощённая версия подтверждения привязки Telegram — автоматически находит ожидающий запрос по `chat_id`, не требуя `request_id`.\n\n**Когда использовать:**\nИспользуйте этот эндпоинт вместо `POST /auth/confirm-link`, если у бота нет доступа к `request_id` из callback_data — например, при обработке текстовых команд вместо inline-кнопок.\n\n**Внутренний эндпоинт:**\nПредназначен для вызова Telegram-ботом. Если для указанного `chat_id` нет ожидающих запросов — возвращается ошибка 400.","operationId":"AuthController_confirmLinkSimple","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chat_id":{"type":"number","example":123456789,"description":"Telegram chat ID пользователя"},"confirmed":{"type":"boolean","example":true,"description":"true — подтвердить привязку, false — отклонить"}},"required":["chat_id","confirmed"]}}}},"responses":{"200":{"description":"Привязка подтверждена или отклонена."},"400":{"description":"Нет ожидающих запросов на привязку для указанного chat_id."}},"summary":"Подтвердить привязку по chat_id (упрощённая версия, для бота)","tags":["Аутентификация"]}},"/auth/pending-links":{"get":{"description":"Возвращает список ожидающих (неподтверждённых) запросов на привязку Telegram-аккаунта для указанного `chat_id`.\n\n**Внутренний эндпоинт:**\nИспользуется Telegram-ботом для проверки наличия запросов перед показом кнопок подтверждения пользователю.\n\n**Типичный сценарий:**\n1. Бот получает команду или callback от пользователя.\n2. Бот вызывает этот эндпоинт, передавая `chat_id`.\n3. Если есть ожидающие запросы — бот показывает кнопки «Подтвердить» / «Отклонить».\n4. Если нет — бот сообщает, что запросов нет.\n\nЗапросы на привязку имеют ограниченный срок действия. Просроченные запросы не возвращаются.","operationId":"AuthController_getPendingLinks","parameters":[{"name":"chat_id","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Массив ожидающих запросов на привязку. Пустой массив, если запросов нет."}},"summary":"Проверить ожидающие запросы на привязку (для бота)","tags":["Аутентификация"]}},"/auth/register-with-code":{"post":{"description":"Регистрация нового веб-аккаунта с одновременной привязкой к существующему Telegram-аккаунту. Позволяет пользователям бота получить полноценный веб-доступ к платформе.\n\n**Сценарий использования:**\nПользователь начал работу через Telegram-бот и хочет зарегистрироваться на сайте, сохранив свой баланс и историю задач.\n\n**Как это работает:**\n1. Пользователь запрашивает 6-значный код в Telegram-боте (команда `/link`).\n2. На странице регистрации вводит email, пароль и код.\n3. Система создаёт веб-аккаунт и привязывает его к Telegram-аккаунту.\n4. Баланс кредитов и история задач из бота становятся доступны в веб-интерфейсе.\n\n**Без кода:**\nЕсли `telegram_code` не передан — создаётся обычный веб-аккаунт без привязки к Telegram.\n\n**Ошибки:**\n- `400` — код невалиден или просрочен.\n- `409` — email уже зарегистрирован, или Telegram-аккаунт уже привязан к другому веб-аккаунту.","operationId":"AuthController_registerWithCode","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWithCodeDto"}}}},"responses":{"200":{"description":"Аккаунт создан. Если передан telegram_code — привязка к Telegram выполнена."},"400":{"description":"Telegram-код невалиден или просрочен."},"409":{"description":"Конфликт: email уже зарегистрирован или Telegram-аккаунт уже привязан."}},"summary":"Регистрация с привязкой Telegram","tags":["Аутентификация"]}},"/api/v1/billing/history":{"get":{"description":"Возвращает постраничный список всех платёжных транзакций для текущего аккаунта, отсортированных по дате — новые первыми.\n\n**Что включено в историю:**\nКаждая запись содержит сумму, статус, описание пакета, дату и идентификатор платежа. Отображаются транзакции во всех состояниях.\n\n**Статусы транзакций:**\n| Статус | Описание |\n|--------|----------|\n| `pending` | Платёж инициирован, ожидает подтверждения от платёжной системы |\n| `completed` | Платёж успешен, кредиты зачислены на баланс |\n| `failed` | Платёж отклонён платёжной системой |\n| `cancelled` | Платёж отменён пользователем или по таймауту |\n\n**Пагинация:**\nИспользуйте параметры `page` и `limit`. Ответ содержит объект `pagination` с полями `total` и `totalPages` для навигации.","operationId":"BillingController_getPaymentHistory","parameters":[{"name":"limit","required":false,"in":"query","description":"Количество записей на странице. По умолчанию: 10.","schema":{"example":10,"type":"number"}},{"name":"page","required":false,"in":"query","description":"Номер страницы. Нумерация начинается с 1. По умолчанию: 1.","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Постраничный список транзакций с метаданными пагинации.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentHistoryResponse"}}}},"401":{"description":"Не авторизован. Проверьте API ключ или JWT токен.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"}}}}},"security":[{"bearer":[]},{"api-key":[]}],"summary":"Получить историю платежей","tags":["Биллинг"]}},"/api/v1/esp/price":{"get":{"description":"Возвращает стоимость указанного количества кредитов на валидацию email. Используется ESP-провайдерами для отображения цен в своём интерфейсе.\n\n**Объёмные скидки:**\nЦена за один email снижается при увеличении объёма. Точная формула зависит от условий вашего партнёрского соглашения. Поле `price_per_email` в ответе показывает итоговую цену за один адрес.\n\n**Валюта:**\nВсе цены указываются в рублях (RUB). Поле `currency` в ответе всегда содержит `RUB`.\n\n**Аутентификация:**\nЭтот эндпоинт использует токен ESP-провайдера (`x-esp-token`), а не стандартные API ключ или JWT. Токен выдаётся при регистрации партнёра.","operationId":"EspController_getPrice","parameters":[{"name":"count","required":true,"in":"query","description":"Количество кредитов для расчёта стоимости. Минимум: 1.","schema":{"example":10000,"type":"number"}},{"name":"x-esp-token","required":true,"in":"header","description":"Токен аутентификации ESP-провайдера. Выдаётся при регистрации партнёра.","schema":{"type":"string"}}],"responses":{"200":{"description":"Расчёт стоимости: общая цена, цена за email, валюта.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResponse"}}}},"401":{"description":"Токен ESP-провайдера отсутствует, невалиден или деактивирован.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EspErrorResponse"}}}}},"summary":"Рассчитать стоимость кредитов","tags":["ESP Провайдеры"]}},"/api/v1/esp/provision":{"post":{"description":"Создаёт новый аккаунт UChecker или пополняет кредиты существующему. Основной эндпоинт для ESP-провайдеров, вызываемый после подтверждения оплаты пользователем.\n\n**Логика работы:**\n- Если аккаунт с указанным email **не существует** — создаётся новый аккаунт, генерируется API ключ, зачисляются кредиты. Поле `is_new_account: true`.\n- Если аккаунт **существует** — кредиты добавляются к текущему балансу. Поле `is_new_account: false`.\n\n**Идемпотентность:**\nПередайте `external_id` (ID заказа на стороне ESP). При повторном вызове с тем же `external_id` кредиты не будут зачислены повторно — вернётся исходный ответ с `is_duplicate: true`.\n\n**Возвращаемые данные:**\nОтвет содержит `api_key` аккаунта — передайте его пользователю для доступа к API. Также возвращаются `credits_added` (зачислено) и `total_credits` (итоговый баланс).\n\n**Аутентификация:**\nТребуется токен ESP-провайдера в заголовке `x-esp-token`.","operationId":"EspController_provisionAccount","parameters":[{"name":"x-esp-token","required":true,"in":"header","description":"Токен аутентификации ESP-провайдера. Выдаётся при регистрации партнёра.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionAccountDto"}}}},"responses":{"200":{"description":"Аккаунт создан или пополнен. Ответ содержит API ключ, зачисленные и итоговые кредиты.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionResponse"}}}},"400":{"description":"Ошибка при создании аккаунта или зачислении кредитов.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EspErrorResponse"}}}},"401":{"description":"Токен ESP-провайдера отсутствует, невалиден или деактивирован.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EspErrorResponse"}}}}},"summary":"Создать аккаунт или пополнить кредиты","tags":["ESP Провайдеры"]}}},"info":{"title":"UChecker API","description":"\n## О сервисе\n\nUChecker — платформа валидации email-адресов для маркетологов, ESP-провайдеров и разработчиков. Проверяйте email поштучно или массово — до миллионов адресов за одну задачу. API определяет существование почтового ящика на уровне SMTP, DNS/MX и провайдера, возвращая однозначный результат: `good` или `bad`.\n\nБазовый URL: `https://api.uchecker.net`\n\n---\n\n## Быстрый старт\n\n**Шаг 1.** Получите API ключ — он доступен в [личном кабинете](https://app.uchecker.net) сразу после регистрации.\n\n**Шаг 2.** Отправьте запрос на валидацию:\n```bash\ncurl -X POST https://api.uchecker.net/api/v1/validate/single \\\n  -H \"x-api-key: ваш_ключ\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"user@example.com\"}'\n```\n\n**Шаг 3.** Получите результат по `task_id` из ответа:\n```bash\ncurl https://api.uchecker.net/api/v1/tasks/123/results \\\n  -H \"x-api-key: ваш_ключ\"\n```\n\n---\n\n## Аутентификация\n\nAPI поддерживает два равноценных способа аутентификации. Используйте любой из них — оба дают полный доступ ко всем эндпоинтам.\n\n### API Key (рекомендуется для интеграций)\n\nПередайте ключ в заголовке `x-api-key`. Ключ не истекает и действует до ручного сброса.\n\n```\nx-api-key: uk_xxxxxxxxxxxxx\n```\n\n### Bearer Token (рекомендуется для фронтенд-приложений)\n\nПолучите JWT через `POST /auth/login`, передайте в заголовке `Authorization`.\n\n```\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIs...\n```\n\n| Токен | Время жизни | Назначение |\n|-------|-------------|------------|\n| access_token | 1 час | Аутентификация запросов |\n| refresh_token | 7 дней | Обновление access_token через `POST /auth/refresh` |\n\n> **Совет:** при серверной интеграции используйте API Key — это проще и не требует управления токенами.\n\n---\n\n## Лимиты и тарификация\n\nUChecker работает по кредитной модели. Каждая проверка одного email-адреса списывает **1 кредит** с вашего баланса.\n\n- **Rate limits отсутствуют** — вы можете отправлять запросы с любой частотой.\n- Кредиты списываются в момент постановки email в очередь.\n- Email с невалидным синтаксисом (при массовой отправке) **не тарифицируются** и возвращаются в поле `invalid_details`.\n- Текущий баланс доступен через `GET /api/v1/account/balance`.\n\n---\n\n## Результаты валидации\n\nКаждый email получает одно из двух значений `validation_result`:\n\n| Результат | Описание |\n|-----------|----------|\n| `good` | Почтовый ящик существует и принимает почту. Адрес безопасен для рассылки. |\n| `bad` | Почтовый ящик не существует, отключён, или домен не принимает почту. |\n\nДля адресов со статусом `bad` в поле `result` указывается детальная причина: `mailbox_not_found`, `domain_not_found`, `smtp_rejected` и другие.\n\n---\n\n## Жизненный цикл задачи\n\nКаждый запрос на валидацию создаёт задачу (task), которая проходит через состояния:\n\n```\npending → processing → completed\n                    ↘ failed\n```\n\n| Состояние | Код | Описание |\n|-----------|-----|----------|\n| `pending` | 0 | Задача создана, ожидает начала обработки |\n| `processing` | 1 | Email-адреса проверяются. Прогресс доступен в поле `progress_percent` |\n| `completed` | 3 | Все адреса проверены. Результаты доступны для скачивания |\n| `failed` | -1 | Произошла ошибка. Обратитесь в поддержку с `task_id` |\n\n**Рекомендуемый polling-интервал:** каждые 5–10 секунд через `GET /api/v1/tasks/:taskId`. Или укажите `webhook_url` при создании задачи — мы отправим POST-запрос с результатами, когда задача завершится.\n\n---\n\n## Обработка ошибок\n\nAPI возвращает стандартные HTTP-коды и JSON-ответы:\n\n| Код | Значение | Когда возникает |\n|-----|----------|-----------------|\n| 200 | Успех | Запрос выполнен |\n| 400 | Ошибка запроса | Невалидные параметры, неверный формат данных |\n| 401 | Не авторизован | Отсутствует или неверный API ключ / JWT токен |\n| 403 | Доступ запрещён | Недостаточно кредитов на балансе |\n| 404 | Не найдено | Задача не существует или не принадлежит вашему аккаунту |\n| 500 | Внутренняя ошибка | Ошибка сервера — повторите запрос позже |\n\nТело ошибки всегда содержит поля `success: false` и `error` с человекочитаемым описанием.\n\n---\n\n## Поддержка\n\nПо вопросам интеграции и техническим вопросам: **support@uchecker.net**\n    ","version":"1.0.0","contact":{}},"tags":[{"name":"Валидация Email","description":"Проверка email-адресов, управление задачами валидации, получение и скачивание результатов"},{"name":"Аутентификация","description":"Вход, регистрация, управление JWT-токенами и API ключами, привязка Telegram"},{"name":"Биллинг","description":"История платежей и транзакций"},{"name":"ESP Провайдеры","description":"Программный интерфейс для ESP-провайдеров: расчёт стоимости и автоматическое создание аккаунтов с зачислением кредитов"}],"servers":[{"url":"https://api.uchecker.net","description":"Production"}],"components":{"securitySchemes":{"api-key":{"type":"apiKey","in":"header","name":"x-api-key","description":"Персональный API ключ. Отображается в личном кабинете (https://app.uchecker.net). Передавайте в заголовке `x-api-key` каждого запроса. Ключ не имеет срока действия — действует до ручного сброса через `POST /auth/reset-api-key`."},"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"JWT access token, полученный через `POST /auth/login`. Время жизни — 1 час. Для обновления используйте `POST /auth/refresh` с refresh token."}},"schemas":{"SingleValidationDto":{"type":"object","properties":{"email":{"type":"string","description":"Email-адрес для проверки. Должен соответствовать стандартному формату RFC 5322 (например, `user@domain.com`).","example":"user@example.com"},"client_type":{"type":"string","description":"Тип клиента. `web` — запрос из веб-интерфейса, `api` — программный вызов. Влияет на формат внутренних уведомлений.","enum":["web","api"]},"webhook_url":{"type":"string","description":"URL для webhook-уведомления о завершении проверки. На указанный URL будет отправлен POST-запрос с результатами. URL должен быть доступен извне и возвращать HTTP 200.","example":"https://your-site.com/webhook/validation-complete"}},"required":["email"]},"SingleValidationQueuedResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения запроса"},"task_id":{"type":"number","example":123,"description":"Уникальный идентификатор задачи. Используйте для получения результатов через `GET /api/v1/tasks/:taskId`."},"email":{"type":"string","example":"user@example.com","description":"Email-адрес, отправленный на проверку"},"status":{"type":"string","example":"queued","description":"Текущий статус email. Значение `queued` означает, что адрес принят в обработку."},"credits_used":{"type":"number","example":1,"description":"Количество кредитов, списанных за этот запрос"},"credits_remaining":{"type":"number","example":999,"description":"Остаток кредитов на балансе после списания"},"estimated_completion":{"type":"string","example":"2024-01-01T12:00:30.000Z","description":"Ориентировочное время завершения проверки. Формат ISO 8601."}},"required":["success","task_id","email","status","credits_used","credits_remaining","estimated_completion"]},"UnauthorizedResponse":{"type":"object","properties":{"statusCode":{"type":"number","example":401,"description":"HTTP-код ошибки"},"message":{"type":"string","example":"Не авторизован","description":"API ключ или JWT токен отсутствует, невалиден или просрочен. Проверьте заголовки `x-api-key` или `Authorization`."}},"required":["statusCode","message"]},"ForbiddenResponse":{"type":"object","properties":{"statusCode":{"type":"number","example":403,"description":"HTTP-код ошибки"},"message":{"type":"string","example":"Недостаточно лимитов","description":"На балансе недостаточно кредитов для выполнения операции. Пополните баланс в личном кабинете."}},"required":["statusCode","message"]},"BulkValidationDto":{"type":"object","properties":{"emails":{"description":"Массив email-адресов для проверки. Адреса с невалидным синтаксисом будут автоматически исключены (без списания кредитов) и перечислены в `invalid_details` ответа.","example":["user1@example.com","user2@example.com","info@company.ru"],"type":"array","items":{"type":"string"}},"client_type":{"type":"string","description":"Тип клиента. `web` — запрос из веб-интерфейса, `api` — программный вызов. Влияет на формат внутренних уведомлений.","enum":["web","api"]},"webhook_url":{"type":"string","description":"URL для webhook-уведомления о завершении задачи. На указанный URL будет отправлен POST-запрос с результатами всех проверок. URL должен быть доступен извне и возвращать HTTP 200.","example":"https://your-site.com/webhook/validation-complete"},"websocket_id":{"type":"string","description":"WebSocket ID для получения обновлений в реальном времени. Используется веб-интерфейсом для отображения прогресса без polling."},"idempotency_key":{"type":"string","description":"Ключ идемпотентности. Уникальная строка (например, UUID), предотвращающая создание дублирующих задач при повторных запросах. Если задача с таким ключом уже существует — вернётся её текущий статус вместо создания новой.","example":"import-2024-01-15-batch-3"}},"required":["emails"]},"InvalidEmailDetail":{"type":"object","properties":{"email":{"type":"string","example":"bad-email","description":"Email-адрес с невалидным синтаксисом"},"reason":{"type":"string","example":"Invalid email syntax","description":"Причина отклонения адреса"}},"required":["email","reason"]},"BulkValidationResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного создания задачи"},"task_id":{"type":"number","example":124,"description":"Уникальный идентификатор задачи для отслеживания прогресса и получения результатов"},"status":{"type":"string","example":"queued","description":"Статус задачи. `queued` — задача принята и ожидает обработки."},"total_emails":{"type":"number","example":100,"description":"Общее количество email-адресов, переданных в запросе"},"valid_emails":{"type":"number","example":95,"description":"Количество email с валидным синтаксисом, принятых в очередь на проверку"},"invalid_emails":{"type":"number","example":5,"description":"Количество email с невалидным синтаксисом, исключённых из проверки (кредиты не списаны)"},"invalid_details":{"description":"Детали по каждому отклонённому email: адрес и причина. Возвращается только при наличии невалидных адресов.","type":"array","items":{"$ref":"#/components/schemas/InvalidEmailDetail"}},"credits_used":{"type":"number","example":95,"description":"Количество списанных кредитов. Равно `valid_emails`."},"credits_remaining":{"type":"number","example":905,"description":"Остаток кредитов на балансе после списания"},"estimated_completion":{"type":"string","example":"2024-01-01T12:01:35.000Z","description":"Ориентировочное время завершения всей задачи. Формат ISO 8601."},"note":{"type":"string","example":"5 невалидных адресов были пропущены","description":"Информационное сообщение о пропущенных адресах (если есть)"}},"required":["success","task_id","status","total_emails","valid_emails","invalid_emails","credits_used","credits_remaining","estimated_completion"]},"TaskStatusResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения запроса"},"task_id":{"type":"number","example":123,"description":"Идентификатор задачи"},"status":{"type":"string","example":"processing","enum":["pending","processing","completed","failed"],"description":"Текущее состояние задачи: `pending` (ожидает), `processing` (обрабатывается), `completed` (завершена), `failed` (ошибка)"},"total_emails":{"type":"number","example":100,"description":"Общее количество email-адресов в задаче"},"processed_emails":{"type":"number","example":45,"description":"Количество уже проверенных email-адресов"},"progress_percent":{"type":"number","example":45,"description":"Прогресс выполнения в процентах (0–100). Используйте для отображения прогресс-бара."},"created_at":{"format":"date-time","type":"string","example":"2024-01-01T12:00:00.000Z","description":"Дата и время создания задачи. Формат ISO 8601."},"finished_at":{"format":"date-time","type":"string","example":null,"nullable":true,"description":"Дата и время завершения задачи. `null`, если задача ещё выполняется. Формат ISO 8601."}},"required":["success","task_id","status","total_emails","processed_emails","progress_percent","created_at","finished_at"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false,"description":"Всегда `false` для ошибок"},"error":{"type":"string","example":"Сообщение об ошибке","description":"Человекочитаемое описание ошибки на русском языке"}},"required":["success","error"]},"ValidationResultItem":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com","description":"Проверяемый email-адрес"},"validation_result":{"type":"string","example":"good","enum":["good","bad"],"description":"Итоговый результат валидации: `good` — почтовый ящик существует, `bad` — не существует или недоступен"},"result":{"type":"string","example":"mailbox_not_found","description":"Детальная причина для невалидных адресов: `mailbox_not_found`, `domain_not_found`, `smtp_rejected` и др. Для `good` адресов — не заполняется."}},"required":["email","validation_result"]},"TaskResultsJsonResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения запроса"},"format":{"type":"string","example":"json","description":"Формат данных в ответе"},"data":{"description":"Массив результатов валидации. Каждый элемент содержит email, результат и причину (для невалидных).","type":"array","items":{"$ref":"#/components/schemas/ValidationResultItem"}}},"required":["success","format","data"]},"TaskAnalyticsReason":{"type":"object","properties":{"key":{"type":"string","example":"smtp_reject","description":"Ключ категории причины отклонения адреса"},"count":{"type":"number","example":12,"description":"Количество адресов с этой причиной"}},"required":["key","count"]},"TaskAnalyticsResponse":{"type":"object","properties":{"total":{"type":"number","example":100,"description":"Общее количество email-адресов в задаче"},"good":{"type":"number","example":80,"description":"Количество валидных адресов (good)"},"bad":{"type":"number","example":15,"description":"Количество невалидных адресов (bad)"},"unknown":{"type":"number","example":5,"description":"Количество адресов с неопределённым результатом (unknown)"},"deliverability":{"type":"number","example":80,"description":"Доставляемость (good / total * 100). Целое число 0–100."},"reasons":{"description":"Разбивка невалидных адресов по категориям причин. Отсортирована по убыванию количества.","type":"array","items":{"$ref":"#/components/schemas/TaskAnalyticsReason"}}},"required":["total","good","bad","unknown","deliverability","reasons"]},"TaskListItem":{"type":"object","properties":{"task_id":{"type":"number","example":123,"description":"Идентификатор задачи"},"fileName":{"type":"string","example":"bulk_100_emails","description":"Имя файла или автоматически сгенерированное название задачи"},"status":{"type":"string","example":"completed","enum":["pending","processing","completed","failed"],"description":"Текущее состояние задачи"},"created_at":{"format":"date-time","type":"string","example":"2024-01-01T12:00:00.000Z","description":"Дата и время создания задачи. Формат ISO 8601."},"finished_at":{"format":"date-time","type":"string","example":"2024-01-01T12:01:35.000Z","nullable":true,"description":"Дата и время завершения задачи. `null` для незавершённых задач. Формат ISO 8601."}},"required":["task_id","fileName","status","created_at","finished_at"]},"TasksListResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения запроса"},"tasks":{"description":"Массив задач. Отсортирован по дате создания — новые первыми.","type":"array","items":{"$ref":"#/components/schemas/TaskListItem"}},"total":{"type":"number","example":50,"description":"Общее количество задач на аккаунте (для расчёта пагинации)"},"page":{"type":"number","example":1,"description":"Текущая страница"},"limit":{"type":"number","example":10,"description":"Количество задач на странице"}},"required":["success","tasks","total","page","limit"]},"AccountBalanceResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения запроса"},"account_id":{"type":"number","example":123456,"description":"Числовой идентификатор аккаунта"},"credits_remaining":{"type":"number","example":1000,"description":"Текущий баланс кредитов. 1 кредит = 1 проверка email-адреса."},"api_key":{"type":"string","example":"uk_xxxxx...","description":"API ключ в маскированном виде (первые 8 символов). Полный ключ доступен при сбросе через `POST /auth/reset-api-key`."}},"required":["success","account_id","credits_remaining","api_key"]},"AccountStatsLastList":{"type":"object","properties":{"total":{"type":"number","example":100,"description":"Общее количество email-адресов в последней завершённой задаче"},"good":{"type":"number","example":80,"description":"Количество валидных адресов (good) в последней задаче"},"bad":{"type":"number","example":15,"description":"Количество невалидных адресов (bad) в последней задаче"},"unknown":{"type":"number","example":5,"description":"Количество адресов с неопределённым результатом (unknown) в последней задаче"}},"required":["total","good","bad","unknown"]},"AccountStatsResponse":{"type":"object","properties":{"tasks_count":{"type":"number","example":42,"description":"Общее количество задач на аккаунте"},"emails_checked":{"type":"number","example":12500,"description":"Общее количество проверенных email-адресов по всем задачам аккаунта"},"avg_deliverability":{"type":"number","example":78,"description":"Средняя доставляемость (good / total * 100) по завершённым задачам. Целое число 0–100."},"last_list":{"nullable":true,"description":"Разбивка по последней завершённой задаче. `null`, если завершённых задач нет.","allOf":[{"$ref":"#/components/schemas/AccountStatsLastList"}]}},"required":["tasks_count","emails_checked","avg_deliverability","last_list"]},"LoginDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com","description":"Email-адрес, указанный при регистрации"},"password":{"type":"string","example":"password123","description":"Пароль аккаунта"}},"required":["email","password"]},"UserInfo":{"type":"object","properties":{"id":{"type":"number","example":1,"description":"Числовой идентификатор аккаунта"},"email":{"type":"string","example":"user@example.com","description":"Email-адрес аккаунта"},"telegram_code":{"type":"number","example":123456,"description":"6-значный код для привязки Telegram-аккаунта. Присутствует, если аккаунт поддерживает Telegram-интеграцию."}},"required":["id","email"]},"LoginResponse":{"type":"object","properties":{"user":{"description":"Информация об аккаунте пользователя","allOf":[{"$ref":"#/components/schemas/UserInfo"}]},"api_key":{"type":"string","example":"uk_xxxxxxxxxxxxx","description":"Персональный API ключ. Используйте в заголовке `x-api-key` для аутентификации запросов. Не имеет срока действия."},"access_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs...","description":"JWT access token. Время жизни: 1 час. Передавайте в заголовке `Authorization: Bearer <token>`."},"refresh_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs...","description":"JWT refresh token. Время жизни: 7 дней. Используйте для обновления access token через `POST /auth/refresh`."}},"required":["user","api_key","access_token","refresh_token"]},"RefreshTokenDto":{"type":"object","properties":{"refresh_token":{"type":"string","description":"Действующий refresh token, полученный при входе или предыдущем обновлении. Каждый refresh token можно использовать только один раз.","example":"eyJhbGciOiJIUzI1NiIs..."}},"required":["refresh_token"]},"RefreshTokenResponse":{"type":"object","properties":{"access_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs...","description":"Новый JWT access token. Время жизни: 1 час."},"refresh_token":{"type":"string","example":"eyJhbGciOiJIUzI1NiIs...","description":"Новый JWT refresh token. Время жизни: 7 дней. Предыдущий refresh token аннулирован."}},"required":["access_token","refresh_token"]},"ForgotPasswordDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com","description":"Email-адрес аккаунта. Если email зарегистрирован, на него будет отправлена ссылка для сброса пароля."}},"required":["email"]},"ResetPasswordDto":{"type":"object","properties":{"token":{"type":"string","example":"a1b2c3...","description":"Одноразовый токен из ссылки в письме. Действует 60 минут."},"password":{"type":"string","example":"newpassword123","description":"Новый пароль. Минимум 6 символов."}},"required":["token","password"]},"ResetApiKeyResponse":{"type":"object","properties":{"message":{"type":"string","example":"API ключ успешно сброшен","description":"Подтверждение операции"},"api_key":{"type":"string","example":"uk_new_xxxxxxxxxxxxx","description":"Новый API ключ. Сохраните его — в дальнейшем он отображается только в маскированном виде."}},"required":["message","api_key"]},"InitiateLinkDto":{"type":"object","properties":{"telegram_code":{"type":"number","example":123456,"description":"6-значный числовой код, полученный от Telegram-бота UChecker командой `/link`"}},"required":["telegram_code"]},"ConfirmLinkDto":{"type":"object","properties":{"request_id":{"type":"number","example":1,"description":"Идентификатор запроса на привязку. Получен из callback_data inline-кнопки в Telegram."},"chat_id":{"type":"number","example":123456789,"description":"Telegram chat ID пользователя. Используется для дополнительной верификации запроса."},"confirmed":{"type":"boolean","example":true,"description":"`true` — подтвердить привязку, `false` — отклонить. При подтверждении Telegram-аккаунт привязывается к веб-аккаунту."}},"required":["request_id","chat_id","confirmed"]},"RegisterWithCodeDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com","description":"Email-адрес для нового веб-аккаунта. Должен быть уникальным."},"password":{"type":"string","example":"password123","description":"Пароль для нового аккаунта. Минимум 6 символов."},"telegram_code":{"type":"number","example":123456,"description":"6-значный код из Telegram-бота для привязки существующего бот-аккаунта к новому веб-аккаунту. Если не указан — создаётся обычный аккаунт без привязки к Telegram."}},"required":["email","password"]},"PaymentHistoryItem":{"type":"object","properties":{"id":{"type":"number","example":1,"description":"Уникальный идентификатор транзакции"},"amount":{"type":"number","example":1000,"description":"Сумма платежа в рублях (RUB)"},"status":{"type":"string","example":"completed","enum":["pending","completed","failed","cancelled"],"description":"Статус транзакции: `pending` (ожидает), `completed` (успешно), `failed` (отклонён), `cancelled` (отменён)"},"product_details":{"type":"string","example":"5000 addresses (5k) via freekassa from web","description":"Описание покупки: количество кредитов, платёжная система и источник (web/bot)"},"creation_date":{"format":"date-time","type":"string","example":"2024-01-01T12:00:00.000Z","description":"Дата и время создания транзакции. Формат ISO 8601."},"payment_id":{"type":"string","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","description":"Уникальный идентификатор платежа в платёжной системе"}},"required":["id","amount","status","product_details","creation_date","payment_id"]},"PaginationInfo":{"type":"object","properties":{"page":{"type":"number","example":1,"description":"Текущая страница"},"limit":{"type":"number","example":10,"description":"Количество записей на странице"},"total":{"type":"number","example":5,"description":"Общее количество записей"},"totalPages":{"type":"number","example":1,"description":"Общее количество страниц. Рассчитывается как `Math.ceil(total / limit)`."}},"required":["page","limit","total","totalPages"]},"PaymentHistoryResponse":{"type":"object","properties":{"data":{"description":"Массив транзакций. Отсортирован по дате — новые первыми.","type":"array","items":{"$ref":"#/components/schemas/PaymentHistoryItem"}},"pagination":{"description":"Метаданные пагинации","allOf":[{"$ref":"#/components/schemas/PaginationInfo"}]}},"required":["data","pagination"]},"PriceResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного расчёта"},"credits":{"type":"number","example":10000,"description":"Запрошенное количество кредитов"},"price":{"type":"number","example":2000,"description":"Общая стоимость в рублях (RUB)"},"price_per_email":{"type":"number","example":0.2,"description":"Стоимость одного кредита (проверки одного email) в рублях. Уменьшается при увеличении объёма."},"currency":{"type":"string","example":"RUB","description":"Код валюты. Всегда `RUB`."}},"required":["success","credits","price","price_per_email","currency"]},"EspErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false,"description":"Всегда `false` для ошибок"},"error":{"type":"string","example":"Неверный токен провайдера","description":"Описание ошибки"}},"required":["success","error"]},"ProvisionAccountDto":{"type":"object","properties":{"email":{"type":"string","description":"Email-адрес пользователя. Если аккаунт с таким email существует — кредиты будут добавлены к текущему балансу. Если нет — будет создан новый аккаунт.","example":"user@example.com"},"credits":{"type":"number","description":"Количество кредитов для зачисления на аккаунт. 1 кредит = 1 проверка email-адреса.","example":10000,"minimum":1},"external_id":{"type":"string","description":"Внешний идентификатор заказа/транзакции на стороне ESP-провайдера. Используется для идемпотентности — повторный запрос с тем же `external_id` не создаст дубликат.","example":"order_12345"}},"required":["email","credits"]},"ProvisionResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true,"description":"Признак успешного выполнения операции"},"message":{"type":"string","example":"Аккаунт создан и лимиты зачислены успешно","description":"Человекочитаемое описание результата"},"account_id":{"type":"number","example":123,"description":"Числовой идентификатор аккаунта UChecker"},"email":{"type":"string","example":"user@example.com","description":"Email-адрес аккаунта"},"api_key":{"type":"string","example":"uk_xxxxxxxxxxxxx","description":"API ключ аккаунта. Передайте пользователю для доступа к API. Для новых аккаунтов — только что сгенерированный ключ."},"credits_added":{"type":"number","example":10000,"description":"Количество кредитов, зачисленных в рамках этой операции"},"total_credits":{"type":"number","example":10000,"description":"Итоговый баланс кредитов на аккаунте (включая ранее зачисленные)"},"is_new_account":{"type":"boolean","example":false,"description":"`true` — создан новый аккаунт, `false` — кредиты добавлены к существующему"},"is_duplicate":{"type":"boolean","example":false,"description":"`true` — запрос с таким `external_id` уже обработан ранее. Кредиты не были зачислены повторно (идемпотентный ответ)."}},"required":["success","message","account_id","email","api_key","credits_added","total_credits","is_new_account"]}}}}