{
  "openapi": "3.0.3",
  "info": {
    "title": "Паспорт API",
    "version": "1.0",
    "description": "Сервис принимает скан паспорта, приводит его к PDF и при развороте делит на две страницы.\n\nАвторизация: заголовок `Authorization: Bearer <ключ>` (ключ на странице «API»).\nДля ожидания результата у создания задачи используйте `?wait=60` (до 60 секунд).\n"
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "tags": [
    {
      "name": "Система"
    },
    {
      "name": "Задачи"
    },
    {
      "name": "Аккаунт"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Проверка доступности API",
        "tags": [
          "Система"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Сервис доступен",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/jobs": {
      "post": {
        "operationId": "createJob",
        "summary": "Отправить скан на обработку",
        "description": "Multipart-форма: поле `file` (обязательно), `mode` (`auto` | `single` | `spread`),\nопционально `align`, `levels` (`1`/`true` — включить);\n`crop` по умолчанию включён (`0`/`false` — выключить).\n\nQuery `wait` — ждать готовности до N секунд (0…60). Без ожидания ответ `202` со статусом `queued`/`processing`.\n",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "description": "Сколько секунд ждать результат (максимум 60)",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 60,
              "default": 0
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Скан (PDF, JPG, PNG, WEBP, TIFF, BMP)"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "single",
                      "spread"
                    ],
                    "default": "auto"
                  },
                  "align": {
                    "type": "string",
                    "description": "1 / true — выровнять готовую страницу"
                  },
                  "crop": {
                    "type": "string",
                    "description": "по умолчанию включено; 0/false — не убирать лишний фон"
                  },
                  "levels": {
                    "type": "string",
                    "description": "1 / true — поправить яркость и контраст"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Задача завершена в пределах wait",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "202": {
            "description": "Задача принята, ещё обрабатывается",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "operationId": "listJobs",
        "summary": "Список задач",
        "description": "История обработок текущего пользователя (администратор видит все).",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Сколько записей вернуть (1…200)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Список задач",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/jobs/cleanup": {
      "post": {
        "operationId": "cleanupJobs",
        "summary": "Очистить историю",
        "description": "Удаляет задачи и файлы. Без `all` — записи старше срока хранения\n(`historyRetentionDays`, по умолчанию 30). С `\"all\": true` — всю историю,\nкроме задач в статусе `processing`. Обычный пользователь чистит только свои записи,\nадминистратор — все.\n",
        "tags": [
          "Задачи"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "all": {
                    "type": "boolean",
                    "description": "Удалить всю историю, а не только устаревшую"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Результат очистки",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "integer",
                      "description": "Сколько записей удалено"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Статус и результат задачи",
        "description": "Пока статус `queued` или `processing` — опрашивайте снова.\nПри `done` в ответе есть `pages` (картинки) и `pdfs` (PDF по страницам).\nПри `failed` смотрите поле `error`.\n",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Задача",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "operationId": "deleteJob",
        "summary": "Удалить задачу",
        "description": "Удаляет задачу и файлы. Нельзя удалить задачу в статусе `processing`.",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Удалено",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/jobs/{id}/retry": {
      "post": {
        "operationId": "retryJob",
        "summary": "Повторить обработку",
        "description": "Запускает ту же задачу снова. Можно передать JSON с новыми настройками:\n`mode`, `align`, `crop`, `levels`. Без тела — прежние настройки.\n",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "single",
                      "spread"
                    ]
                  },
                  "align": {
                    "type": "boolean"
                  },
                  "crop": {
                    "type": "boolean"
                  },
                  "levels": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Задача снова в очереди",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/jobs/{id}/files/{name}": {
      "get": {
        "operationId": "getJobFile",
        "summary": "Скачать готовую страницу или PDF",
        "description": "Имена файлов: `01.jpg`, `02.jpg`, `01.pdf`, `02.pdf` (и при одной странице иногда `result.pdf`).\nДоступно только при статусе `done`.\n",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Имя файла результата"
          }
        ],
        "responses": {
          "200": {
            "description": "Файл",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/jobs/{id}/source": {
      "get": {
        "operationId": "getJobSource",
        "summary": "Скачать исходный скан",
        "tags": [
          "Задачи"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Исходный файл",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/settings": {
      "get": {
        "operationId": "getSettings",
        "summary": "Настройки хранения истории",
        "description": "Возвращает срок хранения истории в днях (`0` — автоочистка выключена).",
        "tags": [
          "Система"
        ],
        "responses": {
          "200": {
            "description": "Настройки",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "historyRetentionDays": {
                      "type": "integer",
                      "example": 30
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "put": {
        "operationId": "putSettings",
        "summary": "Изменить срок хранения истории",
        "description": "Только администратор. `0` отключает автоочистку. Диапазон 0…3650.",
        "tags": [
          "Система"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "historyRetentionDays"
                ],
                "properties": {
                  "historyRetentionDays": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 3650
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Сохранено",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "historyRetentionDays": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/account/token": {
      "post": {
        "operationId": "rotateToken",
        "summary": "Обновить ключ API",
        "description": "Старый ключ сразу перестаёт работать. Новый ключ возвращается в поле `apiToken`.",
        "tags": [
          "Аккаунт"
        ],
        "responses": {
          "200": {
            "description": "Новый ключ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Ключ API со страницы «API»"
      }
    },
    "parameters": {
      "JobId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Идентификатор задачи"
      }
    },
    "responses": {
      "Error": {
        "description": "Ошибка",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "login": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "admin"
            ]
          },
          "apiToken": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "01.jpg"
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "example": "/api/jobs/{id}/files/01.jpg"
          }
        }
      },
      "FileRef": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "done",
              "failed"
            ]
          },
          "error": {
            "type": "string"
          },
          "sourceName": {
            "type": "string"
          },
          "sourceSize": {
            "type": "integer",
            "format": "int64"
          },
          "sourceUrl": {
            "type": "string"
          },
          "sourceKind": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "auto",
              "single",
              "spread"
            ]
          },
          "align": {
            "type": "boolean"
          },
          "crop": {
            "type": "boolean"
          },
          "levels": {
            "type": "boolean"
          },
          "spread": {
            "type": "boolean",
            "description": "true, если кадр разрезан как разворот"
          },
          "pageCount": {
            "type": "integer"
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Page"
            }
          },
          "pdfs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileRef"
            }
          },
          "resultPdf": {
            "type": "string",
            "description": "URL единственного PDF, если страница одна"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "finishedAt": {
            "type": "string",
            "format": "date-time"
          },
          "owner": {
            "type": "string",
            "description": "Логин владельца (только для администратора)"
          }
        }
      }
    }
  }
}