Справочник API
Базовый URL: https://api.hotdoc.io. Тела запросов и ответов — JSON, имена полей в camelCase. Исключения: POST /v1/jobs/upload (ответ) и тело вебхук-запроса — snake_case.
Эндпоинты обработки
| Метод | Эндпоинт | Назначение |
|---|---|---|
POST | /v1/jobs | создать задачу |
GET | /v1/jobs/{id} | статус задачи и список файлов (без результатов распознавания) |
GET | /v1/jobs/{id}/result | полный результат: распознанный текст и ответы модели по файлам |
GET | /v1/jobs | список задач аккаунта (пагинация: pageSize, pageToken, фильтр statusEq) |
POST | /v1/jobs/upload | загрузка одного файла (multipart/form-data) |
POST /v1/jobs — создание задачи
Тело запроса:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
sourceUrls | string[] | да | ссылки на файлы для обработки |
prompts | string[] | нет | инструкции для модели (выполняется только первый промпт); без промптов LLM не вызывается |
neural | object | да | конфигурация модели (см. «Подключение модели») |
ocr | object | да | конфигурация OCR-провайдера (BYOK); обязателен всегда (см. «Конфигурация OCR») |
title | string | нет | произвольное название задачи |
metadata | map<string,string> | нет | произвольные строковые пары «ключ-значение» |
webhookUrl | string | нет | абсолютный http/https-адрес эндпоинта для уведомления о завершении задачи (см. «Вебхуки») |
webhookSecret | string | нет | опциональный HMAC-секрет для подписи вебхук-запросов; принимается только на вход, в ответах не возвращается |
idempotencyKey | string | нет | клиентский ключ для безопасных повторных запросов; максимум 255 символов (см. «Идемпотентность») |
neural и ocr обязательны всегда, даже если prompts пуст. При пустом prompts модель не вызывается: каждому файлу проставляется JOB_LLM_STATUS_SKIPPED со skipReason=no_prompt, и при успешном OCR задача завершается JOB_STATUS_COMPLETE.
Ответ — созданная задача в статусе JOB_STATUS_NEW.
Конфигурация OCR
ocr задаёт бэкенд распознавания (OCR) для каждого запроса (BYOK):
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
ocr.provider | enum | да | один из NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI |
ocr.model | string | да | идентификатор модели провайдера, напр. mistral-ocr-latest (Mistral) или vision-модель выбранного провайдера |
ocr.providerKey | string | да | BYOK-ключ для OCR-провайдера; только на вход, в ответах не возвращается |
Mistral — типовой OCR-бэкенд (mistral-ocr-latest); остальные провайдеры выполняют
vision-chat OCR. Ключ хранится в зашифрованном виде и удаляется вместе с задачей.
Какую модель использовать? Сравните интеллект, цену за токен и провайдеров в сравнении LLM и укажите подходящую модель.
Идемпотентность
Передайте idempotencyKey, чтобы сделать POST /v1/jobs безопасным для повтора. Повторный
запрос с тем же ключом и идентичными параметрами вернёт исходную задачу (дубликат
не создаётся). Тот же ключ с другими параметрами отклоняется с кодом 400
"idempotency key reused with different request parameters". Ключ — максимум 255 символов;
генерируйте новый UUID для каждого логического создания задачи.
GET /v1/jobs/{id}/result — результат
Возвращает { "result": { "job": …, "ocr": [...], "llm": [...] } }. Пример (сокращён):
{
"result": {
"job": {
"id": "15b07304-…", "accountId": "…", "title": "Invoice #42",
"metadata": {}, "status": "JOB_STATUS_COMPLETE", "error": "",
"sourceUrls": ["https://example.com/invoice.pdf"],
"files": ["https://…/files/15b07304-…/invoice.pdf"],
"prompts": ["…"], "neural": { "type": "NEURAL_CLIENT_TYPE_XIAOMI", "model": "mimo-v2-flash", "chunkBudgetTokens": 240000 },
"created": "2026-06-17T16:57:49Z", "updated": "2026-06-17T16:58:05Z"
},
"ocr": [
{ "jobId": "15b07304-…", "file": "https://…/invoice.pdf",
"status": "JOB_OCR_STATUS_DONE", "content": "<p><b>…</b></p>",
"outputFormat": "html", "model": "pdf_fitz", "error": "", "duration": "149508116",
"created": "…", "updated": "…" }
],
"llm": [
{ "jobId": "15b07304-…", "file": "https://…/invoice.pdf",
"promptIndex": 0, "chunkIndex": 0, "chunkTotal": 1,
"status": "JOB_LLM_STATUS_DONE", "skipReason": "",
"model": "mimo-v2-flash", "content": "{\"total\": 15000}",
"error": "", "duration": "601925111", "created": "…", "updated": "…" }
]
}
}
neural в ответе не содержит apiKey; объект Job не содержит поля ocr — ключ OCR не возвращается никогда. ocr[].content — это распознанный текст в формате ocr[].outputFormat; формат зависит от типа файла: PDF и HTML → html, .xml → xml, .txt → plain, остальное (включая изображения и офисные файлы) → markdown. llm[].content — текст ответа модели как есть (структуру держит ваш промпт; серверной валидации нет). В примере выше пустые/нулевые поля ("", {}, 0) показаны для полноты — protojson опускает их, поэтому в реальном ответе они могут отсутствовать.
Поля ocr[]:
| Поле | Тип | Описание |
|---|---|---|
jobId / file | string | id задачи / URL файла |
status | enum | JOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
content | string | распознанный текст в формате outputFormat |
outputFormat | string | формат content: markdown / html / xml / plain. Определяет стратегию структурно-осведомлённого разбиения на части на LLM-стадии |
model | string | метод/движок OCR (напр. pdf_fitz) |
request / rawData | string | отладочные: запрос к OCR и сырой ответ |
error | string | ошибка стадии (пусто, если успех) |
duration | string | длительность, нс (число строкой — protojson отдаёт int64 как строку) |
created / updated | string | RFC3339 |
Поля llm[]:
| Поле | Тип | Описание |
|---|---|---|
jobId / file | string | id задачи / URL файла |
promptIndex | int | индекс промпта (сейчас всегда 0) |
chunkIndex / chunkTotal | int | номер части / всего частей (если текст делился) |
status | enum | JOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
skipReason | string | при SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content |
content | string | ответ модели (текст как есть) |
model | string | строка модели из запроса |
request / rawData | string | отладочные |
error | string | ошибка стадии (напр. provider error (category=…, status=400)) |
duration | string | длительность, нс (число строкой — protojson отдаёт int64 как строку) |
created / updated | string | RFC3339 |
Примечание. Машинная спецификация (OpenAPI/Swagger) пока не генерируется; справочник ведётся вручную. OpenAPI планируется отдельной задачей.
POST /v1/jobs/upload — загрузка файла
Отдельный HTTP-эндпоинт (не grpc-gateway). Принимает файл через multipart/form-data.
Ответ (snake_case, исключение из общего camelCase):
{"url": "https://…/files/…/document.pdf", "name": "document.pdf", "size_bytes": 204800}
Используйте полученный url в sourceUrls при создании задачи.
Тело ошибки этого эндпоинта — {"code": <int>, "message": "…"} без массива details. Лимит размера файла задаётся конфигом (grpc.maxRecvMsgBytes; в текущем деплое — 20 MiB), не код-дефолтом.
Версионирование
Путь /v1 стабилен. Обратно несовместимые изменения выходят под новым путём (/v2). Аддитивные изменения (новые опциональные поля и эндпоинты) совместимость не ломают и анонсируются в Changelog.