ТехПаспорт — Справочник API
GitHub: TechCon-ML-Team/TechCon_Passports Версия: 1.0.0
Адреса
| Среда | URL |
|---|---|
| Production | https://passports.techcon-ml.ru |
| Dev stand | https://passports.dev.techcon-ml.ru |
| Локально | http://localhost:8000 |
Интерактивная документация
Swagger UI (OpenAPI 3.1): https://passports.techcon-ml.ru/docs
ReDoc: https://passports.techcon-ml.ru/redoc
OpenAPI JSON (импорт в Postman/Insomnia): https://passports.techcon-ml.ru/openapi.json
Аутентификация
Два типа токенов, оба передаются в заголовке Authorization: Bearer <токен>:
| Тип | Получение | Срок жизни |
|---|---|---|
| Статический Bearer | Предоставляется администратором | Бессрочно |
| JWT | POST /auth/token |
24 ч (настраивается через JWT_EXPIRE_HOURS) |
Матрица защиты
| Эндпоинт | Метод | Токен обязателен |
|---|---|---|
/auth/token |
POST | Нет |
/health |
GET | Нет |
/events/tasks |
GET | Нет (только cookie сессии) |
/upload |
POST | Да |
/health/deep |
GET | Да |
/api/* |
GET/POST/DELETE | Да |
/export/* |
GET | Да |
/admin/* |
POST | Да |
/task/{id}/update |
POST | Да |
/task/{id}/rename |
POST | Да |
/task/{id} |
DELETE | Да |
Сессия и cookie
Сервис устанавливает cookie bti_session_id (httponly, samesite=lax, 1 год) при первом GET /.
API-клиентам нужно передавать этот cookie, чтобы GET /api/tasks и GET /export/my возвращали задачи только их сессии.
Если cookie нет при вызове /upload — сервер генерирует новый UUID сессии на лету (задачи будут созданы, но недоступны через /api/tasks без cookie).
Статусы задачи
| Статус | Описание |
|---|---|
PENDING |
В очереди, ожидает свободного воркера |
PROCESSING |
Запрос отправлен в LLM, ждём ответ |
SUCCESS |
Данные успешно извлечены |
FAILED |
Не удалось после ретраев (можно повторить через /api/task/{id}/retry) |
ERROR |
Непредвиденная системная ошибка (можно повторить через /api/task/{id}/retry) |
PERMANENT_ERROR |
Превышен лимит ретраев (3 попытки). Повтор невозможен |
Формат ответов
Успех
Все JSON-эндпоинты возвращают ответ в обёртке:
{"ok": true, "data": { ... }}
Ошибка
{"ok": false, "error": {"code": "UPPER_SNAKE_CODE", "message": "Описание"}}
Коды ошибок
| Код | HTTP | Описание |
|---|---|---|
| UNAUTHORIZED | 401 | Отсутствует или невалидный Bearer-токен |
| TOKEN_EXPIRED | 401 | JWT истёк |
| INVALID_API_KEY | 401 | Неверный API-ключ в /auth/token |
| TASK_NOT_FOUND | 404 | Задача не найдена |
| NOT_FOUND | 404 | Ресурс не найден (изображение и т.д.) |
| VALIDATION_ERROR | 400 | Невалидные входные данные |
| INVALID_TASK_STATUS | 400 | Операция невозможна в текущем статусе |
| NO_SESSION | 400 | Отсутствует cookie bti_session_id |
| RETRY_CONFLICT | 409 | Задача не в статусе ERROR/FAILED |
| STORAGE_UNAVAILABLE | 503 | Хранилище недоступно |
| INTERNAL_ERROR | 500 | Внутренняя ошибка |
| DATABASE_UNAVAILABLE | 500 | БД недоступна |
| LLM_UNAVAILABLE | 503 | Провайдер LLM недоступен |
Исключения из обёртки
Не оборачиваются: бинарные файлы (Excel, PDF), HTML-страницы, SSE-поток, 204 No Content.
1. Аутентификация
POST /auth/token
Выдаёт краткосрочный JWT по статическому API-ключу.
Аутентификация: нет
Тело запроса (application/json):
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
api_key |
string | да | Статический токен, предоставленный администратором |
Ответ 200:
{
"ok": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 86400
}
}
| Поле | Тип | Описание |
|---|---|---|
data.token |
string | JWT для заголовка Authorization: Bearer <token> |
data.expires_in |
int | Срок жизни токена в секундах |
Коды ошибок:
| Код | HTTP | Условие |
|---|---|---|
| INVALID_API_KEY | 401 | Неверный API-ключ |
Типовой сценарий интеграции:
TOKEN=$(curl -s -X POST "$BASE/auth/token" \
-H "Content-Type: application/json" \
-d '{"api_key": "my-secret"}' | jq -r .data.token)
2. Загрузка файлов
POST /upload
Загружает один или несколько PDF-файлов для извлечения данных. При повторной загрузке идентичного файла (SHA-256 совпадение) сразу возвращает status: SUCCESS из кэша без обращения к LLM.
Аутентификация: да
Тело запроса (multipart/form-data):
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
files |
file[] | да | PDF-файлы. Максимум 20 файлов, до 50 МБ каждый. Файлы больше 5 файлов разбиваются на батчи с задержкой 10 с |
Ответ 200 (только при заголовке Accept: application/json):
{
"ok": true,
"data": {
"tasks": [
{"id": "3f1a2b4c-1234-5678-abcd-ef1234567890", "status": "PENDING"},
{"id": "7e9d0c1a-5678-abcd-1234-ef1234567890", "status": "SUCCESS"}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data.tasks[].id |
string | UUID задачи для polling |
data.tasks[].status |
string | PENDING — новый файл, SUCCESS — из кэша (результат доступен немедленно) |
Заголовки ответа: X-Poll-Interval: 10, Retry-After: 10 (если есть PENDING задачи).
Без заголовка Accept: application/json → редирект 303 на / для браузеров.
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Более 20 файлов за один запрос |
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
TASK_ID=$(curl -s -X POST "$BASE/upload" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-F "files=@passport.pdf" | jq -r '.data.tasks[0].id')
POST /api/v1/upload-images
Загружает изображения для последующей сборки в PDF. Возвращает список с превью (base64 data URL) для drag-and-drop упорядочивания перед сборкой.
Аутентификация: да
Тело запроса (multipart/form-data):
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
files |
file[] | да | Изображения: jpg, jpeg, png, tiff, bmp, webp. Максимум 20 файлов, до 50 МБ |
Ответ 200:
{
"ok": true,
"data": {
"images": [
{
"id": "3f1a2b4c-1234-5678-abcd-ef1234567890",
"filename": "scan_page1.png",
"thumb_url": "data:image/jpeg;base64,/9j/4AAQ...",
"order": 0
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data.images[].id |
string | UUID изображения во временном хранилище (привязан к cookie сессии) |
data.images[].filename |
string | Оригинальное имя файла |
data.images[].thumb_url |
string | Data URL превью (≈10–30 КБ) для отображения в UI |
data.images[].order |
int | Начальный порядок (по имени файла, 0-based) |
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Ни один файл не является поддерживаемым изображением, или превышен лимит |
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
curl -X POST "$BASE/api/v1/upload-images" \
-H "Authorization: Bearer $TOKEN" \
-b "bti_session_id=<session>" \
-F "files=@page1.jpg" -F "files=@page2.png"
POST /api/v1/create-pdf
Собирает PDF из ранее загруженных изображений (из /api/v1/upload-images) и запускает задачу на извлечение данных.
Аутентификация: да
Тело запроса (application/json):
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
image_ids |
string[] | да | Упорядоченный список UUID изображений из /api/v1/upload-images. Порядок определяет последовательность страниц в PDF |
Ответ 200:
{
"ok": true,
"data": {
"task_id": "3f1a2b4c-1234-5678-abcd-ef1234567890",
"status": "PENDING"
}
}
| Поле | Тип | Описание |
|---|---|---|
data.task_id |
string | UUID задачи для polling через /api/task/{id}/status |
data.status |
string | PENDING — задача поставлена в очередь, SUCCESS — результат из кэша |
Заголовки ответа: X-Poll-Interval: 10, Retry-After: 10 (если status: PENDING).
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Пустой список image_ids или неверный UUID изображения |
| 401 | Токен отсутствует или недействителен |
| 503 | Хранилище PDF недоступно (дисковая ошибка) |
Типовой сценарий интеграции:
curl -X POST "$BASE/api/v1/create-pdf" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-b "bti_session_id=<session>" \
-d '{"image_ids": ["3f1a2b4c-...", "7e9d0c1a-..."]}'
POST /api/v1/rotate-image/{image_id}
Поворачивает временно хранящееся изображение на 90°. Требует тот же bti_session_id, что использовался при загрузке.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
image_id |
string | UUID изображения из /api/v1/upload-images |
Параметры запроса:
| Параметр | Тип | Обязательно | Описание |
|---|---|---|---|
direction |
string | да | cw — по часовой, ccw — против часовой |
Ответ 200:
{
"ok": true,
"data": {
"image_id": "3f1a2b4c-1234-5678-abcd-ef1234567890",
"thumb_url": "data:image/jpeg;base64,/9j/4AAQ..."
}
}
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Нет активной сессии или неверный direction |
| 401 | Токен отсутствует или недействителен |
| 404 | Изображение не найдено в сессии |
Типовой сценарий интеграции:
curl -X POST "$BASE/api/v1/rotate-image/$IMAGE_ID?direction=cw" \
-H "Authorization: Bearer $TOKEN" \
-b "bti_session_id=<session>"
DELETE /api/v1/image/{image_id}
Удаляет временно хранящееся изображение из сессии.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
image_id |
string | UUID изображения |
Ответ 204: пустое тело.
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Нет активной сессии |
| 401 | Токен отсутствует или недействителен |
| 404 | Изображение не найдено в сессии |
Типовой сценарий интеграции:
curl -X DELETE "$BASE/api/v1/image/$IMAGE_ID" \
-H "Authorization: Bearer $TOKEN" \
-b "bti_session_id=<session>"
3. Задачи
GET /api/tasks
Возвращает задачи, привязанные к сессии из cookie bti_session_id. Сортировка по убыванию даты создания.
Аутентификация: да
Параметры запроса:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
page |
int | 1 | Номер страницы (начиная с 1) |
limit |
int | 50 | Записей на страницу (макс. 200) |
status |
string | — | Фильтр: PENDING, PROCESSING, SUCCESS, FAILED, ERROR |
Ответ 200:
{
"ok": true,
"data": {
"items": [
{
"id": "3f1a2b4c-1234-5678-abcd-ef1234567890",
"filename": "passport_42.pdf",
"status": "SUCCESS",
"cached": false,
"created_at": "2026-03-05T12:34:56+00:00"
}
],
"total": 1,
"page": 1,
"limit": 50
}
}
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
curl -s "$BASE/api/tasks?status=SUCCESS&limit=100" \
-H "Authorization: Bearer $TOKEN" \
-b "bti_session_id=<session>" | jq '.data.items[].id'
GET /api/task/{task_id}/status
Быстрая проверка статуса задачи. Использовать для polling.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200:
{"ok": true, "data": {"status": "SUCCESS"}}
Возможные значения data.status: PENDING, PROCESSING, SUCCESS, FAILED, ERROR, PERMANENT_ERROR.
Заголовок X-Poll-Interval: 10 при статусах PENDING / PROCESSING.
Коды ошибок:
| Код | HTTP | Условие |
|---|---|---|
| TASK_NOT_FOUND | 404 | Задача не найдена |
Типовой сценарий интеграции:
import httpx, time
with httpx.Client(base_url=BASE, headers=auth) as client:
while True:
r = client.get(f"/api/task/{task_id}/status")
status = r.json()["data"]["status"]
if status in ("SUCCESS", "FAILED", "ERROR", "PERMANENT_ERROR"):
break
time.sleep(10)
GET /api/task/{task_id}/result
Полный результат извлечения данных из паспорта.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200 (задача завершена):
{
"ok": true,
"data": {
"id": "3f1a2b4c-1234-5678-abcd-ef1234567890",
"filename": "passport_42.pdf",
"status": "SUCCESS",
"cached": false,
"created_at": "2026-03-05T12:34:56+00:00",
"error_message": null,
"data": {
"reasoning": "Внутренняя цепочка рассуждений LLM (для отладки качества)",
"object_info": {
"classification": "Жилое",
"commissioning_year": 1985,
"floors_count": 9,
"plan_shape": "Прямоугольная",
"project_year": 1970,
"owner_name": "МКД",
"address_extra": "-",
"responsibility_level": "Нормальный",
"basement_status": "Есть",
"constructive_type": "Бескаркасный",
"entrances_count": 4,
"project_type": "Типовой",
"owner_address": null
},
"object_specs": {
"building_area": 607.32,
"usable_area": 2890.40,
"walls": "Стены кирпичные",
"height": 15.30,
"volume": 9292.00,
"reconstructions": "нет данных",
"lintels": "Нет",
"columns": "Нет",
"loggias": "нет",
"balconies": "Нет",
"height_config": "Постоянная",
"load_bearing_system": "Фундамент, стены, перекрытия и покрытия"
}
}
}
}
Ответ 200 (задача в процессе):
{"ok": true, "data": {"id": "3f1a2b4c-...", "status": "PROCESSING", "data": null}}
Заголовок: X-Poll-Interval: 10.
nullв поляхdata— поле отсутствует в документе или не распознано LLM. Числа округлены до 2 знаков.
Коды ошибок:
| Код | Условие |
|---|---|
| 404 | Задача не найдена |
| 503 | Задача завершилась с ошибкой провайдера LLM (PROVIDER_RATE_LIMIT / PROVIDER_SERVER_ERROR) |
Типовой сценарий интеграции:
curl -s "$BASE/api/task/$TASK_ID/result" \
-H "Authorization: Bearer $TOKEN" | jq '.data.data.object_info'
4. Редактирование задач
POST /task/{task_id}/update
Вручную обновляет поля извлечённого результата. Только для задач со статусом SUCCESS.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Тело запроса (application/x-www-form-urlencoded):
Ключи имеют формат <секция>:<поле>. Допустимые секции: object_info, object_specs. Пустое значение сбрасывает поле в null. Числа принимаются как с точкой, так и с запятой.
Ответ 200 (при заголовке Accept: application/json):
{"ok": true, "data": {"id": "3f1a2b4c-1234-5678-abcd-ef1234567890"}}
Без заголовка Accept: application/json → редирект 303 на /task/{id}?saved=1.
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Задача не в статусе SUCCESS |
| 401 | Токен отсутствует или недействителен |
| 404 | Задача не найдена |
Типовой сценарий интеграции:
curl -X POST "$BASE/task/$TASK_ID/update" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
--data-urlencode "object_info:commissioning_year=1985" \
--data-urlencode "object_specs:building_area=607.32"
DELETE /task/{task_id}
Удаляет задачу и файл с диска.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200: пустое тело (text/html, HTMX-совместимость).
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
| 404 | Задача не найдена (возвращает application/json) |
Типовой сценарий интеграции:
curl -X DELETE "$BASE/task/$TASK_ID" -H "Authorization: Bearer $TOKEN"
POST /task/{task_id}/rename
Переименовывает задачу. Всегда возвращает редирект 303 (нет JSON-режима).
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Тело запроса (application/x-www-form-urlencoded):
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
name |
string | да | Новое имя задачи, до 255 символов |
Ответ 303: редирект на /.
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Пустое имя или длиннее 255 символов |
| 401 | Токен отсутствует или недействителен |
| 404 | Задача не найдена |
Типовой сценарий интеграции:
curl -X POST "$BASE/task/$TASK_ID/rename" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "name=Здание на ул. Ленина"
POST /api/task/{task_id}/retry
Повторно ставит в очередь задачу в статусе ERROR или FAILED.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200:
{"ok": true, "data": {"status": "PENDING"}}
Коды ошибок:
| Код | HTTP | Условие |
|---|---|---|
| UNAUTHORIZED | 401 | Токен отсутствует или недействителен |
| TASK_NOT_FOUND | 404 | Задача не найдена |
| RETRY_CONFLICT | 409 | Задача не в статусе ERROR / FAILED |
Типовой сценарий интеграции:
curl -X POST "$BASE/api/task/$TASK_ID/retry" -H "Authorization: Bearer $TOKEN"
5. Экспорт
GET /export/all
Все успешные задачи (статус SUCCESS) в виде сводной Excel-таблицы.
Аутентификация: да
Ответ 200: файл application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Имя файла: batch_export_Обработанный_<дата>.xlsx
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
curl "$BASE/export/all" -H "Authorization: Bearer $TOKEN" -o all_passports.xlsx
GET /export/my
Задачи текущей сессии (статус SUCCESS) в Excel. Зависит от cookie bti_session_id.
Аутентификация: да
Ответ 200: файл Excel. Если cookie отсутствует — возвращает пустой файл без ошибки.
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
curl "$BASE/export/my" \
-H "Authorization: Bearer $TOKEN" \
-b "bti_session_id=<session>" \
-o my_passports.xlsx
GET /export/{task_id}/excel
Одна задача в вертикальном Excel-формате (поле → значение).
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200: файл Excel.
Имя файла: <имя_задачи>_Обработанный_<дата>.xlsx
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
| 404 | Задача не найдена |
Типовой сценарий интеграции:
curl "$BASE/export/$TASK_ID/excel" -H "Authorization: Bearer $TOKEN" -o passport.xlsx
GET /export/{task_id}/pdf
Одна задача в виде печатного PDF-отчёта.
Аутентификация: да
Параметры пути:
| Параметр | Тип | Описание |
|---|---|---|
task_id |
string | UUID задачи |
Ответ 200: файл PDF.
Имя файла: <имя_задачи>_Обработанный_<дата>.pdf
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
| 404 | Задача не найдена |
Типовой сценарий интеграции:
curl "$BASE/export/$TASK_ID/pdf" -H "Authorization: Bearer $TOKEN" -o report.pdf
6. Мониторинг
GET /events/tasks
Server-Sent Events поток статусов задач сессии. Не требует Bearer-токена (браузерный EventSource не может слать заголовки). Поток закрывается через 10 минут или когда нет активных задач.
Аутентификация: нет (определяется по cookie bti_session_id)
Ответ 200 (text/event-stream):
data: {"id": "3f1a2b4c-...", "status": "PROCESSING"}
data: {"id": "3f1a2b4c-...", "status": "SUCCESS"}
: keepalive
event: done
data: {}
| Тип события | Когда |
|---|---|
data: {...} |
Статус задачи изменился |
: keepalive |
Каждые 2 с без изменений |
event: done |
Нет активных задач или сессия пуста |
Коды ошибок:
| Код | Условие |
|---|---|
| 400 | Cookie bti_session_id отсутствует |
Типовой сценарий интеграции:
const es = new EventSource('/events/tasks');
es.onmessage = (e) => console.log(JSON.parse(e.data));
es.addEventListener('done', () => es.close());
GET /health
Проверяет доступность БД и наличие активных воркеров Celery. Не требует авторизации.
Аутентификация: нет
Ответ 200 (сервис работает нормально):
{
"ok": true,
"data": {
"database": "ok",
"workers_online": 1,
"version": "1.0.0"
}
}
Ответ 503 / 500 (деградация):
{
"ok": false,
"error": {
"code": "WORKERS_UNAVAILABLE",
"message": "No Celery workers online"
}
}
| Код ошибки | HTTP | Условие |
|---|---|---|
WORKERS_UNAVAILABLE |
503 | workers_online = 0 |
DATABASE_UNAVAILABLE |
500 | БД не отвечает |
workers_online: 0при ответе 200 означает graceful degradation: сервис принимает загрузки, но обработка в очереди. Gatus мониторинг:ok == true.
Типовой сценарий интеграции:
curl -s https://passports.techcon-ml.ru/health | jq .data.workers_online
GET /health/deep
Расширенная проверка: БД + воркеры + провайдер LLM. Требует авторизации.
Аутентификация: да
Ответ 200:
{
"ok": true,
"data": {
"database": "ok",
"workers_online": 1,
"version": "1.0.0",
"llm_status": "ok",
"llm_provider": "openrouter",
"llm_error": null
}
}
llm_status: "ok" | "unconfigured" | "error".
"unconfigured" — нет OPENROUTER_API_KEY. Для Gatus: condition != error.
Коды ошибок:
| Код | Условие |
|---|---|
| 503 | LLM-провайдер недоступен (llm_status: error) |
| 500 | БД недоступна |
Типовой сценарий интеграции:
curl -s "$BASE/health/deep" -H "Authorization: Bearer $TOKEN" | jq .data.llm_status
7. Администрирование
POST /admin/cleanup
Удаляет задачи старше указанного числа дней. Рекомендуется запускать по расписанию (cron).
Аутентификация: да
Параметры запроса:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
days |
int | 30 | Удалить задачи старше N дней (1–365) |
Ответ 200:
{"ok": true, "data": {"deleted_tasks": 12}}
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
| 422 | days вне диапазона 1–365 |
Типовой сценарий интеграции:
curl -X POST "$BASE/admin/cleanup?days=90" -H "Authorization: Bearer $TOKEN"
GET /api/admin/failed-stats
Статистика упавших задач за последний час.
Аутентификация: да
Ответ 200:
{
"ok": true,
"data": {
"failed_last_hour": 3,
"checked_at": "2026-04-06T10:00:00+00:00"
}
}
Учитывает статусы: ERROR, FAILED, PERMANENT_ERROR.
Коды ошибок:
| Код | Условие |
|---|---|
| 401 | Токен отсутствует или недействителен |
Типовой сценарий интеграции:
curl -s "$BASE/api/admin/failed-stats" -H "Authorization: Bearer $TOKEN"
Сценарий полной интеграции (end-to-end)
curl
BASE="https://passports.techcon-ml.ru"
# 1. Получить JWT
TOKEN=$(curl -s -X POST "$BASE/auth/token" \
-H "Content-Type: application/json" \
-d '{"api_key": "my-secret"}' | jq -r .data.token)
# 2. Загрузить файл
TASK_ID=$(curl -s -X POST "$BASE/upload" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-F "files=@passport.pdf" | jq -r '.data.tasks[0].id')
# 3. Polling статуса
while true; do
STATUS=$(curl -s "$BASE/api/task/$TASK_ID/status" \
-H "Authorization: Bearer $TOKEN" | jq -r .data.status)
[ "$STATUS" = "SUCCESS" ] && break
[ "$STATUS" = "PERMANENT_ERROR" ] && echo "Ошибка, повтор невозможен" && exit 1
[ "$STATUS" = "FAILED" ] && curl -s -X POST "$BASE/api/task/$TASK_ID/retry" \
-H "Authorization: Bearer $TOKEN"
sleep 10
done
# 4. Получить данные
curl -s "$BASE/api/task/$TASK_ID/result" \
-H "Authorization: Bearer $TOKEN" | jq .data.data
# 5. Скачать Excel
curl "$BASE/export/$TASK_ID/excel" \
-H "Authorization: Bearer $TOKEN" \
-o "passport_$TASK_ID.xlsx"
Python (httpx)
import httpx
import time
BASE = "https://passports.techcon-ml.ru"
with httpx.Client(base_url=BASE, timeout=30) as client:
# 1. Получить JWT
resp = client.post("/auth/token", json={"api_key": "my-secret"})
token = resp.json()["data"]["token"]
headers = {"Authorization": f"Bearer {token}"}
# 2. Загрузить PDF
with open("passport.pdf", "rb") as f:
resp = client.post(
"/upload",
files={"files": ("passport.pdf", f, "application/pdf")},
headers={**headers, "Accept": "application/json"},
)
task_id = resp.json()["data"]["tasks"][0]["id"]
# 3. Polling
while True:
resp = client.get(f"/api/task/{task_id}/status", headers=headers)
status = resp.json()["data"]["status"]
if status in ("SUCCESS", "FAILED", "ERROR", "PERMANENT_ERROR"):
break
time.sleep(10)
# 4. Результат
resp = client.get(f"/api/task/{task_id}/result", headers=headers)
result = resp.json()["data"]
print(f"Status: {result['status']}")
if result["data"]:
print(f"Object: {result['data']['object_info']}")
print(f"Specs: {result['data']['object_specs']}")