/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-data | output_schema, include, split, match — JSON-строками в form-полях |
| Готовый текст | application/json | обычные вложенные объекты и массивы |
Набор полей в обоих вариантах совпадает 1:1. Единственное исключение —
registry (реестр для сверки): это файл, поэтому match доступен только
через multipart; в чисто-JSON запросе он всегда дает
MATCH_REGISTRY_REQUIRED (422).
Поля запроса
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
file | file (multipart) | ✳️ | Документ — поддерживаемые форматы |
text | string | ✳️ | Markdown или простой текст вместо файла (✳️ — ровно одно из file/text) |
prompt | string | null | — | Задача словами. Требует непустую output_schema, лимит ~10 000 знаков |
output_schema | object | null | — | JSON Schema результата — см. «output_schema» |
document_type | string | null | — | Подсказка парсеру: contract, invoice, act, … — ускоряет разбор |
include | string[] | — | Дополнительные секции ответа: markdown, fragments, tables, fields |
split | object | null | — | {mode, max_documents, return_files} — разбиение PDF, см. ниже |
match | object | null | — | {key_column, values_from} — сверка с реестром, см. ниже |
registry | file (multipart) | — | Реестр для сверки (XLSX/CSV), вторая file-part вместе с match |
mode | fast | balanced | accurate | — | Принимается, но на обработку пока не влияет (см. ниже) |
async | bool | — | true → немедленный {task_id, poll_url} вместо ожидания результата |
language | string | — | Язык документа: ru, en, zh, de, tr, it — подсказка OCR, модели и переводчику. Для китайских сканов обязателен, иначе иероглифы не распознаются; для немецких, турецких и итальянских документов отличает их от английского при переводе |
translate | object | — | {"to": "ru"/"en"/"zh"/"de"/"tr"/"it"} — перевод строковых значений result после извлечения. Требует непустую output_schema (TRANSLATE_REQUIRES_SCHEMA). Ответ получает секцию translated той же формы и usage.translation_tokens (при split — у каждого documents[i], верхний translated — null) — см. «Перевод и Excel» |
format | string | — | json (по умолчанию) или xlsx — синхронный ответ файлом Excel вместо JSON. В async-запросе игнорируется (format_ignored_for_async) — файл забирается GET /v1/process/{task_id}?format=xlsx |
client_meta | object | — | Служебная телеметрия кабинета (например, какой рецепт и сколько колонок схемы использовались); на обработку не влияет, учитывается только при доступе по сессии |
Правила, которые проверяются до обработки
- Ровно один источник —
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). Молчаливое «сделал половину» хуже отказа — вы бы считали, что сверка прошла, а ее не было.
Полный каталог кодов — в разделе «Ошибки».
Виды задач
Вид определяется присутствующими полями, а не адресом. Приоритет:
match → split → prompt → extract → parse (запрос со сверкой
считается сверкой, даже если внутри была экстракция).
Задача по инструкции — 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. Границы секций находятся без обращения к модели; со схемой из каждой секции сразу извлекаются данные — это основной сценарий, а не два вызова подряд.
Как ищутся границы — три слоя, от дешёвого к дорогому:
- Заголовки приложений в тексте («Приложение №», «Акт», «Спецификация», «Дополнительное соглашение») — граница может быть посреди страницы.
- Титул каждой страницы по словарю на русском, английском, немецком,
турецком, китайском и итальянском: инвойс (Rechnung, Fattura, e-Fatura,
发票), упаковочный лист, CMR, авианакладная, экспортная и транзитная
декларация (Ausfuhrbegleitdokument, EX1, T1, 报关单), сертификат
происхождения и A.TR, Lieferschein, заявка/заказ, таможенные уведомления,
акт, приложение, договор, спецификация, протокол испытаний. Маркер
продолжения («Page 2 of 6», «Seite : 2», «页码/页数», «Übertрag») держит
страницу в текущей секции, список позиций остаётся в своей декларации,
три копии одного MAWB с одним номером — одна секция.
doc_typeсекции — тип с номером документа: «Инвойс 383180», «Накладная 226804». - Модель — один короткий вызов на страницы без единого признака (не
больше 30), и только если границ нет вовсе — по первым восьми тысячам
символов текста. При
mode: "pages"модель не вызывается.
Поле split | Тип | По умолчанию | Описание |
|---|---|---|---|
mode | auto | pages | auto | auto — по логическим границам, pages — по фиксированным страницам |
max_documents | int | null | — | Максимум секций в ответе; лишние отбрасываются с warning split_truncated |
return_files | bool | false | Добавить 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 при split — null: данные лежат в 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_column | string | Колонка реестра — ключ сверки, например ARTICLE |
values_from | string | 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.
Три сценария, ради которых секция существует:
- Проверяемые ответы ИИ. Ассистент отвечает «расторжение — п. 8.2» и называет страницу 14; юрист открывает страницу и проверяет. Ответ без ссылки на источник в юридическом контуре ничего не стоит.
- Подсветка поля на скане. Пользователь видит, откуда взялось извлеченное значение, и подтверждает его одним взглядом, а не сверкой с оригиналом вручную.
- Свой поиск. Фрагменты — готовое сырье для вашей поисковой базы: векторизуете их локальной моделью у себя, оригинальным (не анонимизированным) текстом.
{
"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 }status — processing | done | error; в поле result лежит полный
конверт обычного ответа. Задача живет 1 час и принадлежит вашему ключу:
чужой или просроченный task_id — 404 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[]; - Готовые сценарии — сценарии целиком, от запроса до ответа.