APIDOCINSPECT

/v1/process

Полный контракт единого эндпоинта — поля запроса, виды задач (промпт, схема, разбиение, сверка, разбор), конверт ответа и асинхронный режим.

POST /v1/process — единственный эндпоинт обработки документов. Один запрос, любой документ, любая задача: вы описываете, что хотите получить, сервис сам решает, как это сделать. Лестница детерминированного извлечения, OCR, нарезка длинного текста, выбор модели — внутренняя кухня, наружу не торчит.

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "file=@act.pdf" \
  -F "prompt=Проверь, соответствует ли акт договору: те же стороны, тот же предмет" \
  -F 'output_schema={"type":"object","properties":{
        "matches_contract":{"type":"boolean"},
        "discrepancies":{"type":"array","items":{"type":"string"}}}}'

Два варианта входа — один контракт

ВариантТело запросаКак передаются объекты
Файлmultipart/form-dataoutput_schema, include, split, matchJSON-строками в form-полях
Готовый текстapplication/jsonобычные вложенные объекты и массивы

Набор полей в обоих вариантах совпадает 1:1. Единственное исключение — registry (реестр для сверки): это файл, поэтому match доступен только через multipart; в чисто-JSON запросе он всегда дает MATCH_REGISTRY_REQUIRED (422).

Поля запроса

ПолеТипОбяз.Описание
filefile (multipart)✳️Документ — поддерживаемые форматы
textstring✳️Markdown или простой текст вместо файла (✳️ — ровно одно из file/text)
promptstring | nullЗадача словами. Требует непустую output_schema, лимит ~10 000 знаков
output_schemaobject | nullJSON Schema результата — см. «output_schema»
document_typestring | nullПодсказка парсеру: contract, invoice, act, … — ускоряет разбор
includestring[]Дополнительные секции ответа: markdown, fragments, tables, fields
splitobject | null{mode, max_documents, return_files} — разбиение PDF, см. ниже
matchobject | null{key_column, values_from} — сверка с реестром, см. ниже
registryfile (multipart)Реестр для сверки (XLSX/CSV), вторая file-part вместе с match
modefast | balanced | accurateПринимается, но на обработку пока не влияет (см. ниже)
asyncbooltrue → немедленный {task_id, poll_url} вместо ожидания результата
languagestringЯзык документа: ru, en, zh, de, tr, it — подсказка OCR, модели и переводчику. Для китайских сканов обязателен, иначе иероглифы не распознаются; для немецких, турецких и итальянских документов отличает их от английского при переводе
translateobject{"to": "ru"/"en"/"zh"/"de"/"tr"/"it"} — перевод строковых значений result после извлечения. Требует непустую output_schema (TRANSLATE_REQUIRES_SCHEMA). Ответ получает секцию translated той же формы и usage.translation_tokens (при split — у каждого documents[i], верхний translatednull) — см. «Перевод и Excel»
formatstringjson (по умолчанию) или xlsx — синхронный ответ файлом Excel вместо JSON. В async-запросе игнорируется (format_ignored_for_async) — файл забирается GET /v1/process/{task_id}?format=xlsx
client_metaobjectСлужебная телеметрия кабинета (например, какой рецепт и сколько колонок схемы использовались); на обработку не влияет, учитывается только при доступе по сессии

Правила, которые проверяются до обработки

  • Ровно один источникfile или text. Ни одного — SOURCE_REQUIRED, оба сразу — SOURCE_CONFLICT.
  • Запрос должен что-то просить: непустую output_schema, либо split, либо match, либо непустой include. Пустой запрос — EMPTY_TASK, а не 200 с пустым ответом.
  • prompt без непустой output_schema не существуетPROMPT_REQUIRES_SCHEMA. Текстовый ответ описывается схемой из одного поля: {"summary": {"type": "string"}}. Следствие: resultвсегда JSON-объект.
  • Неизвестное поле — ошибка, а не тишина. Опечатка вроде promt дает UNKNOWN_FIELD (422) со списком принимаемых полей. Иначе вы получили бы 200 и результат по внутреннему дефолту вместо своей инструкции.
  • Нереализованное сочетание — честный отказ. Сейчас это split вместе с match: COMBINATION_NOT_SUPPORTED (422). Молчаливое «сделал половину» хуже отказа — вы бы считали, что сверка прошла, а ее не было.

Полный каталог кодов — в разделе «Ошибки».

Виды задач

Вид определяется присутствующими полями, а не адресом. Приоритет: matchsplitpromptextractparse (запрос со сверкой считается сверкой, даже если внутри была экстракция).

Задача по инструкции — prompt + output_schema

Основной способ работы с API. Модели уходит именно ваш промпт, а не внутренний дефолтный: классификация, суммаризация, поиск смысловых условий, сравнение с эталоном — это разные промпты, а не разные эндпоинты.

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{
    "text": "Договор аренды №12 от 01.02.2026 между ООО «Ромашка»…",
    "prompt": "Сделай краткое summary одним предложением",
    "output_schema": {"type":"object","properties":{"summary":{"type":"string"}}}
  }'
{
  "result": { "summary": "Договор аренды нежилого помещения на 11 месяцев с автопродлением." },
  "usage": { "pages": 1, "sheets": null },
  "metadata": { "file_hash": null, "extraction_status": "full", "input_quality_score": null, "cached": false },
  "warnings": []
}

Промпт входит в тарифицируемый объем страниц и ограничен ~10 000 знаками (PROMPT_TOO_LONG): это инструкция, а не содержимое. Тексту место в text.

Извлечение по схеме — output_schema без промпта

Поток однотипных документов, где нужны одни и те же поля: сервис сам знает, как их искать, и обработка идет короче — заметно на конвейерных сценариях.

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "file=@invoice.pdf" \
  -F 'output_schema={"type":"object","properties":{"inn":{"type":"string"}}}'
{
  "result": { "inn": "7707083893" },
  "usage": { "pages": 1, "sheets": 3 },
  "metadata": { "file_hash": "sha256:…", "extraction_status": "full", "input_quality_score": null, "cached": false },
  "warnings": []
}

Разбиение — split

Пачка сканов или сшивка «договор + приложения» одним PDF. Границы секций находятся без обращения к модели; со схемой из каждой секции сразу извлекаются данные — это основной сценарий, а не два вызова подряд.

Как ищутся границы — три слоя, от дешёвого к дорогому:

  1. Заголовки приложений в тексте («Приложение №», «Акт», «Спецификация», «Дополнительное соглашение») — граница может быть посреди страницы.
  2. Титул каждой страницы по словарю на русском, английском, немецком, турецком, китайском и итальянском: инвойс (Rechnung, Fattura, e-Fatura, 发票), упаковочный лист, CMR, авианакладная, экспортная и транзитная декларация (Ausfuhrbegleitdokument, EX1, T1, 报关单), сертификат происхождения и A.TR, Lieferschein, заявка/заказ, таможенные уведомления, акт, приложение, договор, спецификация, протокол испытаний. Маркер продолжения («Page 2 of 6», «Seite : 2», «页码/页数», «Übertрag») держит страницу в текущей секции, список позиций остаётся в своей декларации, три копии одного MAWB с одним номером — одна секция. doc_type секции — тип с номером документа: «Инвойс 383180», «Накладная 226804».
  3. Модель — один короткий вызов на страницы без единого признака (не больше 30), и только если границ нет вовсе — по первым восьми тысячам символов текста. При mode: "pages" модель не вызывается.
Поле splitТипПо умолчаниюОписание
modeauto | pagesautoauto — по логическим границам, pages — по фиксированным страницам
max_documentsint | nullМаксимум секций в ответе; лишние отбрасываются с warning split_truncated
return_filesboolfalseДобавить documents[].pdf_base64 — PDF каждой секции
curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "file=@pack.pdf" \
  -F 'split={"mode":"auto"}' \
  -F 'output_schema={"type":"object","properties":{"number":{"type":"string"}}}'
{
  "result": null,
  "documents": [
    { "index": 1, "pages": [1, 2], "doc_type": "Инвойс 58", "confidence": 0.9,
      "result": { "number": "58" }, "markdown": null, "fragments": null,
      "tables": null, "fields": null, "pdf_base64": null },
    { "index": 2, "pages": [3, 3], "doc_type": "Акт", "confidence": 0.9,
      "result": { "number": "7" }, "markdown": null, "fragments": null,
      "tables": null, "fields": null, "pdf_base64": null }
  ],
  "usage": { "pages": 3, "sheets": 3 },
  "metadata": { "file_hash": "sha256:…", "extraction_status": "full", "input_quality_score": null, "cached": false },
  "warnings": []
}

Верхний result при splitnull: данные лежат в documents[]. usage считается по всему документу, а не по секциям. split валиден и без схемы — тогда вы получаете только границы и типы (extraction_status: "parse_only", documents[].result: null), а режете исходник сами. return_files: true выключен по умолчанию, чтобы ответ не раздувался мегабайтами base64.

Каждая секция обрабатывается как отдельный документ: сначала детерминированный слой (лестница и строчный автомат по страницам секции, сверка с итогами самого документа), и только остаток уходит модели — одним вызовом на секцию. Если дерево закрыло схему секции целиком, модель для неё не вызывается вовсе. documents[].fields при include: ["fields"] — provenance именно этой секции; tables по секциям пока не считаются.

split работает только по PDF-файлу — иначе SPLIT_REQUIRES_PDF (422).

Сверка с реестром — match

Сравнение извлеченных значений с вашим реестром (номенклатура снабжения, справочник контрагентов). Реестр приезжает в самом запросе, обрабатывается строго в памяти и не сохраняется на стороне API.

Поле matchТипОписание
key_columnstringКолонка реестра — ключ сверки, например ARTICLE
values_fromstring | nullПуть к полю в result, например items[].article. Без него сервис подбирает поле по точному совпадению имени с key_column

Сравниваются значения из уже извлеченных данных, поэтому match требует непустую output_schema (MATCH_REQUIRES_SCHEMA) и файл registry (MATCH_REGISTRY_REQUIRED) — то есть multipart-запрос:

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "text=Накладная №58 от 10.02.2026: артикул A1 — 10 шт, артикул ZZ — 3 шт" \
  -F 'output_schema={"type":"object","properties":{"items":{"type":"array",
      "items":{"type":"object","properties":{"article":{"type":"string"}}}}}}' \
  -F 'match={"key_column":"ARTICLE"}' \
  -F "registry=@registry.csv"
{
  "result": { "items": [{ "article": "A1" }, { "article": "ZZ" }] },
  "match": {
    "found": 1,
    "missing_in_registry": ["ZZ"],
    "unmatched_registry": ["B2"],
    "coverage": 0.5,
    "registry_rows": 2,
    "key_column": "ARTICLE",
    "values_field": "items[].article"
  }
}

Если колонку реестра не удается однозначно сопоставить с полем result, API отвечает честной ошибкой — MATCH_FIELD_NOT_FOUND (с подсказкой details.similar, если есть похожие имена) или MATCH_FIELD_AMBIGUOUS (несколько полей подходят — укажите values_from). Молчаливая автоподстановка «по похожести» запрещена: неверная сверка с уверенным видом дороже отказа.

Если реестр не должен покидать ваш контур

Та же логика сверки доступна SDK-библиотекой docai-match и выполняется на вашей стороне. HTTP-вариант — это «реестр приезжает в запросе и не пишется на диск», а не единственный способ.

Разбор — только include

Ни схемы, ни промпта, ни split, ни match — модель не вызывается вовсе. Нужен хотя бы непустой include:

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{"text": "Просто текст", "include": ["markdown"]}'
{ "result": null, "markdown": "Просто текст",
  "metadata": { "extraction_status": "parse_only", "…": "…" } }

Перевод и Excel

Китайский упаковочный лист — типовой случай: язык подсказан language, перевод строк result заказан translate, а забрать результат удобнее готовым файлом Excel, чем JSON-объектом.

# 202 {"task_id": "…", "poll_url": "/v1/process/…"} — сразу забираем task_id
TASK=$(curl -s -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "file=@packing_list.pdf" \
  -F "language=zh" \
  -F 'translate={"to":"ru"}' \
  -F 'output_schema={"type":"object","properties":{
        "shipper":{"type":"string","title":"Отправитель"},
        "items":{"type":"array","title":"Позиции","items":{"type":"object","properties":{
          "name":{"type":"string","title":"Наименование"},
          "qty":{"type":"number","title":"Количество"}}}}}}' \
  -F "async=true" | jq -r .task_id)

# дождитесь status=done: curl -H "X-API-Key: $API_KEY" https://api.docinspect.ru/v1/process/$TASK
# пока status=processing, ?format=xlsx вернёт тот же JSON, а не файл
curl -H "X-API-Key: $API_KEY" "https://api.docinspect.ru/v1/process/$TASK?format=xlsx" -o packing_list.xlsx
{
  "result": {"shipper": "深圳市华强电子有限公司", "items": [{"name": "不锈钢法兰 DN50 PN16", "qty": 10}]},
  "translated": {
    "to": "ru",
    "result": {"shipper": "Шэньчжэньская компания «Хуацян электроникс»",
               "items": [{"name": "Фланец из нержавеющей стали DN50 PN16", "qty": 10}]}
  },
  "usage": { "pages": 1, "sheets": 1, "translation_tokens": 412 }
}

В translated только два поля: to и result той же формы, что исходный result. Чем и как переведено — внутренняя кухня сервиса, в ответ не попадает. Расход провайдера перевода — usage.translation_tokens, в тарифицируемые страницы не входит.

Что переводится, а что остаётся как есть. Переводятся значения с письменностью, чужой для целевого языка: наименования позиций, описания, условия, названия документов. Артикулы, коды в верхнем регистре, даты, числа, e-mail и ссылки не трогаются. Имена собственные латиницей — названия компаний, улицы, города, ФИО, номера документов — остаются как в оригинале: их не переводят и не транслитерируют, так принято в таможенных и транспортных документах. Названия нелатинскими письменностями (иероглифы) передаются общепринятой транслитерацией. Цифры под гардом: если перевод строки потерял или добавил цифру, строка переводится ещё раз, а при повторном искажении остаётся в оригинале — в ответе появится translation_partial с числом таких строк.

Excel (format=xlsx) раскладывается в два листа: «Документ» — реквизиты блоками («поле · значение · перевод») и таблица позиций одна под другой, «Позиции» — та же таблица отдельно, с фильтром и закреплённой шапкой, «Наименование» и «Наименование (ru)» рядом. Если сборка Excel упала — деградация к JSON с warning xlsx_export_failed, результат не теряется.

Переводится только result

markdown/fragments/tables остаются как в исходном документе — перевод затрагивает только структурированные значения result.

В кабинете то же самое доступно без кода — раздел «Обработка документов».

Доступ по сессии кабинета

POST /v1/process и GET /v1/process/{task_id} принимают не только X-API-Key: браузерный контур кабинета ходит сюда с сессионной кукой и обязательным заголовком X-Requested-With: docai-cabinet — без него приходит 401 CABINET_HEADER_REQUIRED (вторая линия защиты после SameSite=Lax: сторонний сайт не может проставить произвольный заголовок формой). Страницы при этом списываются на ваш активный ключ с максимальным тарифом; если ключей еще нет, заводится обычный ключ с меткой «Кабинет».

Для интеграции это ничего не меняет — она ходит ключом. Подробности — в разделе «Кабинет и обработка без кода».

Конверт ответа

Ответ всегда одной формы. Не запрошенные секции приходят null, а не отсутствуют — типизированному клиенту не нужно проверять наличие ключа.

ПолеЧто это
resultДанные по вашей output_schema; null при split, при разборе без схемы и когда документ не удалось разобрать
documents[]Секции — только при split, иначе null
markdownТекст документа — только если запрошен через include
fragments[]Текст, нарезанный с привязкой к страницам — только через include
tables[]Векторные таблицы (только PDF) — только через include
fields[]Provenance: где на странице найдено значение (только PDF) — только через include
matchРезультат сверки; null без match
usage{pages, sheets, ocr} — тарифицируемые страницы, физические листы и признак OCR (скан/фото: страницы считаются вдвое)
metadata{file_hash, extraction_status, input_quality_score, cached}
warnings[]Предупреждения о качестве — см. «Ошибки»

Внутренней кухни в ответе нет: ни счетчика токенов, ни имени модели, ни провайдера — вы их не выбираете и не оплачиваете. Тарифицируются страницы — usage.pages; usage.sheets показывает, сколько это физических листов (для текстового входа — null, листов физически нет). Почему из одного чертежа получается восемь страниц — в разделе «Аутентификация» → «Квоты и биллинг».

input_quality_score пока всегда null

В /v1/process качество входа не вычисляется — поле есть в конверте, но для любого входа приходит null. Это известное ограничение текущей версии; сигнал «попросить пересъемку» стройте по warnings[] (parse_thin, ocr_noise).

include: markdown, fragments, tables, fields

Секция вне этого списка — INCLUDE_UNKNOWN (422).

tables и fields доступны только для PDF-входа. На тексте, XML, таблице или изображении обе секции приходят null с предупреждением section_unavailable, даже если явно запрошены — это не молчаливый пробел.

fragments — «где написано»

result отвечает на вопрос что написано. fragments отвечает на вопрос где написано: это текст документа, нарезанный кусками с привязкой к странице — [{index, text, page}], без векторов.

Обязательное к пониманию: markdown — это склеенный текст, границы страниц в нем стерты. Где кончилась страница, знает только парсер, и восстановить это из плоского markdown уже нельзя. Именно за это отвечает fragments.

Три сценария, ради которых секция существует:

  1. Проверяемые ответы ИИ. Ассистент отвечает «расторжение — п. 8.2» и называет страницу 14; юрист открывает страницу и проверяет. Ответ без ссылки на источник в юридическом контуре ничего не стоит.
  2. Подсветка поля на скане. Пользователь видит, откуда взялось извлеченное значение, и подтверждает его одним взглядом, а не сверкой с оригиналом вручную.
  3. Свой поиск. Фрагменты — готовое сырье для вашей поисковой базы: векторизуете их локальной моделью у себя, оригинальным (не анонимизированным) текстом.
{
  "fragments": [
    { "index": 0, "text": "1. Предмет договора…", "page": 1 },
    { "index": 1, "text": "8.2. Договор может быть расторгнут…", "page": 14 }
  ]
}

page — 1-based номер по положению фрагмента относительно маркеров страниц, которые ставит OCR-путь. Если маркеров нет (текстовый вход, PDF с текстовым слоем), page честно приходит null: номер не угадывается по содержимому — ссылка «(см. страницу 12)» внутри текста номер страницы не меняет.

Эмбеддингов API не считает и векторов не отдает: векторизация — забота клиента, локальная модель у вас работает по оригинальному тексту и потому точнее, чем поиск по анонимизированному.

mode — принят, но пока не влияет

Параметр fast/balanced/accurate валидируется, но на обработку в /v1/process пока не влияет. Любое значение, кроме balanced, дает info-предупреждение mode_ignored — чтобы вы не считали, что получили fast. Компромисс latency/точность появится в следующих версиях эндпоинта; сейчас честнее предупредить, чем принять параметр молча.

Асинхронный режим

Внешний прокси обрывает соединение на 300 секундах, а OCR+LLM большого скана занимают минуты. async: true работает и для файла, и для текста:

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{"text": "ИНН 77", "async": true,
       "output_schema": {"type":"object","properties":{"inn":{"type":"string"}}}}'
# → 202 {"task_id": "a1b2c3…", "poll_url": "/v1/process/a1b2c3…"}
curl https://api.docinspect.ru/v1/process/a1b2c3… -H "X-API-Key: $API_KEY"
{ "task_id": "a1b2c3…", "status": "done",
  "result": { "result": { "inn": "77" }, "usage": { "…": "…" } }, "error": null }

statusprocessing | done | error; в поле result лежит полный конверт обычного ответа. Задача живет 1 час и принадлежит вашему ключу: чужой или просроченный task_id404 TASK_NOT_FOUND. Непредвиденный сбой фоновой обработки приходит как {"status": "error", "error": {"code": "PROCESSING_ERROR", …}} — в HTTP-ответе такого кода не бывает.

Статус и результат задачи переживают перезапуск сервисаtask_id не «сгорает» при деплое. Если перезапуск застал задачу посреди обработки, она честно завершится {"status": "error", "error": {"code": "INTERRUPTED_BY_RESTART", …}} — отправьте документ повторно, это не списывается с квоты дважды. При переполнении очереди обработки POST с async: true отвечает 503 с кодом QUEUE_FULL и заголовком Retry-After — повторите запрос через указанное число секунд.

Примеры

curl -X POST https://api.docinspect.ru/v1/process \
  -H "X-API-Key: $API_KEY" \
  -F "file=@invoice.pdf" \
  -F "prompt=Извлеки реквизиты сторон, позиции и итоговую сумму счета" \
  -F 'output_schema={
        "type": "object",
        "properties": {
          "supplier": {"type": "object"},
          "customer": {"type": "object"},
          "items": {"type": "array"},
          "total_amount": {"type": "number"}
        }
      }'

Дальше

  • output_schema — как спроектировать схему, чтобы не ловить пустые и перепутанные поля;
  • Форматы и лимиты — что принимается на вход, XML, границы по размеру;
  • Ошибки — коды, extraction_status, warnings[];
  • Готовые сценарии — сценарии целиком, от запроса до ответа.

On this page