{
  "openapi": "3.1.0",
  "info": {
    "title": "mail_converter_api",
    "description": "\nBackend для Mail Tester (Mailganer): конвертация Outlook MSG, парсинг/хранение EML,\nIMAP lookup тестовых писем, SPF-анализ и отчёт доставляемости (#18625).\n\n## Авторизация\n\nБольшинство методов требуют заголовок:\n\n```\nAuthorization: Bearer <api-key>\n```\n\nКлючи задаются в env `API_USER_KEYS` / `API_SUPERUSER_KEYS` (через запятую).\n\n## Формат ответа\n\n```json\n{ \"status\": \"success|error\", \"content\": … }\n```\n\n`content` — строка или объект в зависимости от метода.\n",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://mailganer.com/tools/mail-tester/v1",
      "description": "Публичный REST (#21407). На apex /api/ занят кабинетом."
    },
    {
      "url": "https://newland.mailganer.com/tools/mail-tester/v1",
      "description": "Staging (HTML — Basic Auth; REST без Basic Auth)"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Проверка живости сервиса",
        "description": "Публичный healthcheck без авторизации. Проверяет процесс API, доступность хранилища и IMAP.",
        "operationId": "health_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/convert/msg_to_eml": {
      "post": {
        "tags": [
          "convert"
        ],
        "summary": "Конвертация Outlook MSG → EML",
        "description": "Тело запроса — сырые байты файла `.msg`.\n\nНужен, когда клиент (Mail Tester) получает письмо из Outlook\nи должен привести его к стандартному EML перед парсингом или показом.\nВ `content` возвращается EML как одна строка.",
        "operationId": "convert_message_convert_msg_to_eml_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConvertResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "Content Too Large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/emails/parse": {
      "post": {
        "tags": [
          "emails"
        ],
        "summary": "Разобрать EML без сохранения",
        "description": "Тело запроса — байты/текст EML.\n\nСухой разбор письма (заголовки, subject, MIME-части, raw) **без** записи\nв хранилище. Удобно для превью и отладки, когда код теста ещё не нужен.",
        "operationId": "parse_email_emails_parse_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "Content Too Large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/emails/codes": {
      "post": {
        "tags": [
          "emails"
        ],
        "summary": "Выдать уникальный тестовый email",
        "description": "Зарезервировать уникальный `code` и вернуть адрес `mail-test-<code>@…`.\n\nКод атомарно пишется в хранилище (пустой placeholder), поэтому несколько\nклиентов API не получат один и тот же адрес. Дальше:\nотправьте письмо на `email`, затем `POST /emails/{code}/lookup`.",
        "operationId": "allocate_email_code_emails_codes_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AllocateCodeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/emails/upload": {
      "post": {
        "tags": [
          "emails"
        ],
        "summary": "Загрузить .eml/.msg и сохранить",
        "description": "Multipart-поле `file`: `.eml` или `.msg`.\n\nЗагрузка письма пользователем в Mail Tester: MSG при необходимости\nконвертируется в EML, письмо сохраняется, в ответе —\n`{ \"code\": \"…\" }` для дальнейших GET/lookup.\nКод тоже резервируется атомарно (без пересечений между клиентами).",
        "operationId": "upload_email_emails_upload_post",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_email_emails_upload_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "Content Too Large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/emails/{code}": {
      "get": {
        "tags": [
          "emails"
        ],
        "summary": "Получить сохранённое письмо по коду",
        "description": "Достаёт уже сохранённое письмо по коду теста и возвращает разобранный JSON.\n\nИспользуется, когда письмо уже загружено через `/emails/upload` или\nранее сохранено после IMAP. Параметр `wait_upload=1` коротко поллит\nхранилище (без IMAP), если клиент ждёт параллельную загрузку файла.\n\nБез тела: `pending` → 202, `failed` → 404 `lookup_failed`, иначе 404 `not_found`.",
        "operationId": "get_email_emails__code__get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          },
          {
            "name": "wait_upload",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "description": "`1` — подождать появления письма около `UPLOAD_WAIT_SECONDS` (без IMAP). `0` — сразу ответ по статусу.",
              "default": 0,
              "title": "Wait Upload"
            },
            "description": "`1` — подождать появления письма около `UPLOAD_WAIT_SECONDS` (без IMAP). `0` — сразу ответ по статусу."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LookupPendingResponse"
                }
              }
            },
            "description": "Accepted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Not Found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Too Many Requests"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/emails/{code}/lookup": {
      "post": {
        "tags": [
          "emails"
        ],
        "summary": "Поставить IMAP-поиск письма в очередь",
        "description": "Основной сценарий Mail Tester «отправить на тестовый ящик».\n\nЕсли письмо уже сохранено — сразу разбирает и отдаёт.\nИначе ставит lookup в очередь (`lookup_status=pending`, HTTP 202);\nфоновый воркер поллит IMAP каждые ~10 с и сохраняет письмо.\nКлиент должен опрашивать `GET /emails/{code}` до появления письма\nили `lookup_failed` / таймаута (~10 мин).\n\nПовторный lookup на уже `pending` идемпотентен (таймер не сбрасывается).\nПосле `failed` — 409 `retry_not_available` (нужен новый code).",
        "operationId": "lookup_email_emails__code__lookup_post",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LookupBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LookupPendingResponse"
                }
              }
            },
            "description": "Accepted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Conflict"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Too Many Requests"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/emails/{code}/report": {
      "get": {
        "tags": [
          "report"
        ],
        "summary": "Полный отчёт доставляемости одним JSON (#18940)",
        "description": "Весь отчёт одним ответом — удобно для CI/интеграторов.\n\nПример: `GET /emails/{code}/report` или\n`GET /emails/{code}/report?sections=auth,summary`.\nПосекционный вызов `…/report/{section}` остаётся для параллельного UI.",
        "operationId": "email_report_full_emails__code__report_get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          },
          {
            "name": "sections",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Список секций через запятую (auth,headers,spamassassin,rspamd,dnsbl,senderscore,content,redirects,summary). По умолчанию — все. Алиас mail-tester: параметр `test`.",
              "title": "Sections"
            },
            "description": "Список секций через запятую (auth,headers,spamassassin,rspamd,dnsbl,senderscore,content,redirects,summary). По умолчанию — все. Алиас mail-tester: параметр `test`."
          },
          {
            "name": "test",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Алиас `sections` (как &test= у mail-tester.com)",
              "title": "Test"
            },
            "description": "Алиас `sections` (как &test= у mail-tester.com)"
          }
        ],
        "responses": {
          "200": {
            "description": "Объект секций в content (auth, headers, …, summary)",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Not Found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Too Many Requests"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/emails/{code}/report/{section}": {
      "get": {
        "tags": [
          "report"
        ],
        "summary": "Секция отчёта доставляемости (#18625)",
        "description": "Считает одну секцию спам/доставляемость-отчёта по сохранённому письму.\n\nСекции: `auth`, `headers`, `spamassassin`, `rspamd`, `dnsbl`, `senderscore`,\n`content`, `unsubscribe`, `redirects`, `summary`.\nФронт (ЛК / newland) зовёт их параллельно. SpamAssassin / Rspamd\nдеградируют в `available: false`, если `SPAMD_HOST` / `RSPAMD_URL`\nне заданы или демон недоступен.\n`unsubscribe` — RFC 8058 + живой зонд адреса отписки с испорченной подписью.\n`headers` — цепочка Received (узлы) + auth из заголовков; `senderscore` —\nValidity Sender Score по IPv4 отправителя.",
        "operationId": "email_report_emails__code__report__section__get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          },
          {
            "name": "section",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Section"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Секция отчёта в content",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Not Found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Too Many Requests"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/spf/analyze": {
      "post": {
        "tags": [
          "spf"
        ],
        "summary": "Анализ SPF: score, rating, дерево",
        "description": "Разбор SPF-записи домена для блока Mail Tester.\n\nВозвращает только JSON (score, rating, matched_rules, spf_tree, warnings).\nHTML-фрагмент результата рисует клиент по этим данным.",
        "operationId": "spf_analyze_spf_analyze_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpfAnalyzeBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpfAnalyzeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AllocateCodeContent": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Уникальный зарезервированный код теста",
            "examples": [
              "e66fe6a49"
            ]
          },
          "email": {
            "type": "string",
            "title": "Email",
            "description": "Адрес ящика, на который нужно отправить тестовое письмо",
            "examples": [
              "mail-test-e66fe6a49@mailganer.ru"
            ]
          }
        },
        "type": "object",
        "required": [
          "code",
          "email"
        ],
        "title": "AllocateCodeContent"
      },
      "AllocateCodeResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/AllocateCodeContent"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "AllocateCodeResponse"
      },
      "ApiError": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "error"
            ]
          },
          "content": {
            "type": "string",
            "title": "Content",
            "examples": [
              "Authorization failed"
            ]
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "ApiError"
      },
      "Body_upload_email_emails_upload_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File",
            "description": "Файл `.eml` или `.msg`"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_upload_email_emails_upload_post"
      },
      "ConvertResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "type": "string",
            "title": "Content",
            "description": "EML целиком одной строкой"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "ConvertResponse"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "HealthResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "type": "string",
            "title": "Content",
            "examples": [
              "ok"
            ]
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "HealthResponse"
      },
      "LookupBody": {
        "properties": {
          "user_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "User Id",
            "description": "Опциональный ID пользователя Mailganer (аудит). Лимит параллельных IMAP-lookup считается по API-ключу, не по этому полю."
          }
        },
        "type": "object",
        "title": "LookupBody"
      },
      "LookupPendingContent": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "lookup_status": {
            "type": "string",
            "title": "Lookup Status",
            "description": "pending — письмо ещё ищется фоновым воркером",
            "examples": [
              "pending"
            ]
          }
        },
        "type": "object",
        "required": [
          "code",
          "lookup_status"
        ],
        "title": "LookupPendingContent"
      },
      "LookupPendingResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/LookupPendingContent"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "LookupPendingResponse"
      },
      "ParseResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/ParsedEmail"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "ParseResponse"
      },
      "ParsedEmail": {
        "properties": {
          "headers": {
            "items": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": "array",
            "title": "Headers",
            "description": "Список пар [имя, значение] заголовков",
            "examples": [
              [
                [
                  "From",
                  "a@b.com"
                ],
                [
                  "Subject",
                  "Hi"
                ]
              ]
            ]
          },
          "subject": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Subject"
          },
          "body": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object",
            "title": "Body",
            "description": "Текстовые MIME-части: обычно text/plain, text/html, иногда text/x-amp-html",
            "examples": [
              {
                "text/html": "<p>hello</p>",
                "text/plain": "hello"
              }
            ]
          },
          "raw": {
            "type": "string",
            "title": "Raw",
            "description": "Исходный EML целиком"
          }
        },
        "type": "object",
        "required": [
          "headers",
          "body",
          "raw"
        ],
        "title": "ParsedEmail"
      },
      "SpfAnalyzeBody": {
        "properties": {
          "domain": {
            "type": "string",
            "title": "Domain",
            "description": "Домен, для которого нужно разобрать SPF",
            "examples": [
              "mailganer.com"
            ]
          }
        },
        "type": "object",
        "required": [
          "domain"
        ],
        "title": "SpfAnalyzeBody"
      },
      "SpfAnalyzeResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/SpfResult"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "SpfAnalyzeResponse"
      },
      "SpfMatchedRule": {
        "properties": {
          "spf": {
            "type": "string",
            "title": "Spf"
          },
          "score": {
            "type": "integer",
            "title": "Score"
          }
        },
        "type": "object",
        "required": [
          "spf",
          "score"
        ],
        "title": "SpfMatchedRule"
      },
      "SpfResult": {
        "properties": {
          "spf": {
            "type": "string",
            "title": "Spf"
          },
          "score": {
            "type": "integer",
            "title": "Score"
          },
          "rating": {
            "type": "string",
            "title": "Rating"
          },
          "matched_rules": {
            "items": {
              "$ref": "#/components/schemas/SpfMatchedRule"
            },
            "type": "array",
            "title": "Matched Rules"
          },
          "spf_tree": {
            "items": {
              "$ref": "#/components/schemas/SpfTreeItem"
            },
            "type": "array",
            "title": "Spf Tree"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Warnings",
            "default": []
          }
        },
        "type": "object",
        "required": [
          "spf",
          "score",
          "rating",
          "matched_rules",
          "spf_tree"
        ],
        "title": "SpfResult"
      },
      "SpfTreeItem": {
        "properties": {
          "domain": {
            "type": "string",
            "title": "Domain"
          },
          "spf": {
            "type": "string",
            "title": "Spf"
          }
        },
        "type": "object",
        "required": [
          "domain",
          "spf"
        ],
        "title": "SpfTreeItem"
      },
      "UploadContent": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Код теста для GET /emails/{code} и lookup",
            "examples": [
              "a1b2c3d4e"
            ]
          }
        },
        "type": "object",
        "required": [
          "code"
        ],
        "title": "UploadContent"
      },
      "UploadResponse": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "success"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/UploadContent"
          }
        },
        "type": "object",
        "required": [
          "status",
          "content"
        ],
        "title": "UploadResponse"
      },
      "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"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "API-ключ из env `API_USER_KEYS` / `API_SUPERUSER_KEYS`. Передаётся как `Authorization: Bearer <key>`."
      }
    }
  }
}
