Справочник 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 — создание задачи

Тело запроса:

ПолеТипОбязательноеОписание
sourceUrlsstring[]дассылки на файлы для обработки
promptsstring[]нетинструкции для модели (выполняется только первый промпт); без промптов LLM не вызывается
neuralobjectдаконфигурация модели (см. «Подключение модели»)
ocrobjectдаконфигурация OCR-провайдера (BYOK); обязателен всегда (см. «Конфигурация OCR»)
titlestringнетпроизвольное название задачи
metadatamap<string,string>нетпроизвольные строковые пары «ключ-значение»
webhookUrlstringнетабсолютный http/https-адрес эндпоинта для уведомления о завершении задачи (см. «Вебхуки»)
webhookSecretstringнетопциональный HMAC-секрет для подписи вебхук-запросов; принимается только на вход, в ответах не возвращается
idempotencyKeystringнетклиентский ключ для безопасных повторных запросов; максимум 255 символов (см. «Идемпотентность»)

neural и ocr обязательны всегда, даже если prompts пуст. При пустом prompts модель не вызывается: каждому файлу проставляется JOB_LLM_STATUS_SKIPPED со skipReason=no_prompt, и при успешном OCR задача завершается JOB_STATUS_COMPLETE.

Ответ — созданная задача в статусе JOB_STATUS_NEW.

Конфигурация OCR

ocr задаёт бэкенд распознавания (OCR) для каждого запроса (BYOK):

ПолеТипОбязательноеОписание
ocr.providerenumдаодин из NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI
ocr.modelstringдаидентификатор модели провайдера, напр. mistral-ocr-latest (Mistral) или vision-модель выбранного провайдера
ocr.providerKeystringда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, .xmlxml, .txtplain, остальное (включая изображения и офисные файлы) → markdown. llm[].content — текст ответа модели как есть (структуру держит ваш промпт; серверной валидации нет). В примере выше пустые/нулевые поля ("", {}, 0) показаны для полноты — protojson опускает их, поэтому в реальном ответе они могут отсутствовать.

Поля ocr[]:

ПолеТипОписание
jobId / filestringid задачи / URL файла
statusenumJOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
contentstringраспознанный текст в формате outputFormat
outputFormatstringформат content: markdown / html / xml / plain. Определяет стратегию структурно-осведомлённого разбиения на части на LLM-стадии
modelstringметод/движок OCR (напр. pdf_fitz)
request / rawDatastringотладочные: запрос к OCR и сырой ответ
errorstringошибка стадии (пусто, если успех)
durationstringдлительность, нс (число строкой — protojson отдаёт int64 как строку)
created / updatedstringRFC3339

Поля llm[]:

ПолеТипОписание
jobId / filestringid задачи / URL файла
promptIndexintиндекс промпта (сейчас всегда 0)
chunkIndex / chunkTotalintномер части / всего частей (если текст делился)
statusenumJOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
skipReasonstringпри SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content
contentstringответ модели (текст как есть)
modelstringстрока модели из запроса
request / rawDatastringотладочные
errorstringошибка стадии (напр. provider error (category=…, status=400))
durationstringдлительность, нс (число строкой — protojson отдаёт int64 как строку)
created / updatedstringRFC3339

Примечание. Машинная спецификация (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.