TL;DR. Я собрал self‑service REST API, который принимает фото документа РФ и возвращает структурированный JSON. Паспорт, водительское удостоверение, СТС. Регистрация не нужна, чтобы попробовать — есть демо без ключа: reqdoc.ru. Ниже — контракт, примеры кода и то, что оказалось неочевидно при работе именно с российскими документами.
Пост — про запуск и валидацию спроса. Буду благодарен за критику по делу: и по контракту API, и по тому, каких типов документов вам реально не хватает.
Зачем это
Если вы когда‑нибудь встраивали распознавание паспорта или СТС в свой продукт (страхование, каршеринг, финтех, логистика, любой онбординг с проверкой документов) — вы знаете, что вариантов немного и все так себе:
Своё OCR — собрать датасет, обучить/зафайнтюнить модель, держать инференс, чинить кейсы «фото под углом в темноте». Это отдельный проект на месяцы, а не фича.
Крупные облака — часто это enterprise‑продажи, договоры, интеграция «через менеджера», а не «взял ключ и через 10 минут работает».
Готовые SDK — обычно про верификацию личности целиком (KYC), тяжёлые и недешёвые, если тебе нужно просто «фото СТС → поля».
Мне не хватало простого промежуточного слоя: берёшь ключ → шлёшь картинку → получаешь JSON. По модели, знакомой любому, кто дёргал DaData: self‑service, документация, честный контракт, тарифы на сайте. Только не адреса/ФИО из строки, а документы.
И вот тут — главное, ради чего всё затевалось. Я не претендую обогнать большие облака по качеству модели. Ставка в другом: самый короткий путь от «увидел» до «работает в проде». Открыл сайт → потрогал демо без регистрации → взял ключ в боте → сделал первый боевой запрос. Реально за пару минут, без договора, менеджера, КП и «подключения через отдел продаж». Чтобы сделать то же самое у большого провайдера, обычно уходят часы, а то и дни переписки. Если сравнивать по developer experience — вот здесь reqdoc и должен выигрывать.
Так появился reqdoc.
Контракт: один документ → один JSON
Один эндпоинт, файл отправляется как обычная веб‑форма с вложниемmultipart/form-data:
POST https://api.reqdoc.ru/v1/recognize
Два поля: file — сам файл документа в самых распространённых форматах (jpg/png/pdf, до 10 МБ) и type (passport | driver_license | vehicle_registration | vehicle_registration_back).
Попробовать можно прямо curl‑ом, без ключа (демо‑режим, лимиты по IP):
curl -X POST https://api.reqdoc.ru/v1/recognize \ -F "type=passport" \ -F "file=@passport.jpg"
Ответ ‑в едином формате. Даты — как в документе (DD.MM.YYYY), ключи snake_case, нераспознанное поле приходит как null (а не выдумывается):
{ "status": "ok", "type": "passport", "data": { "surname": "Иванов", "first_name": "Иван", "patronymic": "Иванович", "gender": "male", "birth_date": "03.08.1978", "birth_place": "с. Леоново, Иркутская обл.", "series": "9223", "number": "376525", "issue_date": "08.09.2023", "issuing_authority": "МВД по Республике Татарстан", "division_code": "160-008", "registration_address": "г. Москва, ..." }, "request_id": "e8468676-c5d0-4e84-8625-4b2eb7396241" }
С ключом — добавляется Authorization: Bearer reqdoc_live_<...>, и вызовы считаются по месячной квоте тарифа. Ключ выдаёт Telegram‑бот (/key), это самый быстрый способ начать.
Пример на Python:
import requests with open("sts.jpg", "rb") as f: r = requests.post( "https://api.reqdoc.ru/v1/recognize", headers={"Authorization": "Bearer reqdoc_live_..."}, data={"type": "vehicle_registration"}, files={"file": f}, ) print(r.json()["data"])
Полный контракт с полями по каждому типу и кодами ошибок — в документации: docs.reqdoc.ru.
Что оказалось неочевидно с РФ‑документами
Самое интересное — не «прогнать через OCR», а привести хаос к предсказуемому контракту. Несколько вещей, на которые ушло непропорционально много внимания:
1. Серия и номер живут одной строкой. OCR отдаёт паспорт как 9223 376525, ВУ как 99 04 218375, а разработчику удобнее получить series и number отдельно. Причём правило разбивки разное: у паспорта серия — первые 4 цифры, у ВУ — тоже 4, но исходная строка отформатирована иначе. Поэтому поверх OCR лежит слой нормализации, который знает про каждый тип: где резать, что тримить, как раскладывать.
2. Марка и модель — тоже одна строка. В СТС приходит KIA RIO → раскладываем в make (первое слово) и model (остаток). Мелочь, но без неё каждый интегратор пишет один и тот же парсер у себя.
3. Лицевая и оборот СТС — это два разных снимка. Владелец и адрес регистрации физически на обороте. Поэтому два типа (vehicle_registration и vehicle_registration_back): вы загружаете фото нужной стороны — API не притворяется, что видит то, чего на кадре нет.
4. Даты — как в документе, без «умной» нормализации. Соблазн привести всё к ISO велик, но для документов важнее точное соответствие тому, что напечатано (03.08.1978). Нормализацию под свой формат делает потребитель — так меньше сюрпризов.
5. null вместо галлюцинации. Если поля на кадре нет (снял разворот с фото — регистрации там не будет) — приходит null. Никаких «додумок».
По сути ценность API — не в самом OCR, а в предсказуемом контракте поверх него: одинаковый формат ответа, стабильные имена полей, честные null, request_id в каждом ответе.
Защита API и мониторинг
Публичный API без защиты живёт недолго, поэтому на входе — слой, который делает скучные, но обязательные вещи:
Ключи (
reqdoc_live_...), в базе — только SHA-256 хеш, не сам токен.Rate‑limit фиксированным окном (по ключу и по IP для демо) — атомарный инкремент счётчика в Postgres, дешёвый и предсказуемый.
Месячная квота на аккаунт: тариф задаёт включённый объём, дальше — overage или жёсткий стоп (для бесплатных).
request_idв теле ответа и в заголовкеX-Request-Id— чтобы можно было сослаться на конкретный вызов в поддержке.Лог использования без ПДн: пишутся метаданные вызова (тип документа, статус, латентность, биллинг‑флаг), но не содержимое документа.
Ошибки — не «500 и разбирайся», а типизированный код: invalid_key (401), invalid_type (400), rate_limited (429), recognition_empty (422), ocr_failed (502).
Мониторинг, чтобы не падать «в непонятное время». Публичный API бесполезен, если он лежит, а ты узнаёшь об этом от пользователя. Поэтому аптайм отслеживается на нескольких уровнях:
Health‑эндпоинт
GET /v1/healthпрогоняет цепочку насквозь (веб‑слой → оркестратор → БД) и отдаёт 200/500 — то есть проверяет не «отвечает ли порт», а живой ли путь до базы.Внутренняя проверка раз в пару минут следит за API, OCR, БД и свободным местом на диске (однажды именно забитый диск уронил сервис — теперь это отдельная метрика). Алерт приходит мгновенно и только на смене состояния — чтобы не было шума из одинаковых сообщений.
Внешний dead‑man‑мониторинг пингует health со стороны, независимо от нашего сервера: если упадёт вся машина целиком (и внутренний монитор вместе с ней), сигнал всё равно придёт.
Смысл — узнавать о проблеме первым, а не из тикета. Для API, который кто‑то встроил в свой онбординг, это не опция, а часть контракта надёжности.
Про 152-ФЗ
Документы — это персданные, поэтому инфраструктура на РФ‑хостинге, а в логах вызовов не хранится содержимое документа — только техническая телеметрия. Это осознанное ограничение: меньше данных о пользователе на нашей стороне — меньше рисков у всех.
Честные ограничения
Чтобы не создавать ложных ожиданий:
Нет
confidence. OCR не отдаёт вероятность по полям, поэтому и API её не выдумывает. Валидацию критичных полей закладывайте у себя.Пока только 3 типа (паспорт, ВУ, СТС + оборот). Европротокол, справка ГАИ, СНИЛС, ИНН, ПТС — в планах, но эндпоинтов ещё нет.
OCR не идеален. Кривое фото, блики, наклон — влияют. Демо специально даёт потрогать на своих файлах, чтобы вы оценили качество до интеграции.
Тарифы
Модель freemium: бесплатный trial‑ключ на 50 распознаваний/мес выдаёт бот — этого хватает, чтобы проверить в бою. Дальше — платные тарифы (от 990 ₽/мес), детали на сайте. Специально не расписываю прайс здесь — пост не про это.
Куда дальше — и вопрос к вам
Сейчас стадия — валидация спроса. Инфраструктура, контракт, демо, документация, выдача ключей — уже живые. Дальше зависит от обратной связи.
Поэтому вопрос к тем, кто такое встраивал: каких типов документов вам не хватает больше всего? Европротокол? ПТС? СНИЛС/ИНН? Что‑то ещё? И что в контракте вы бы сделали иначе — ISO‑даты, другой формат ответа, вебхуки вместо синхронного ответа?
Потрогать: reqdoc.ru · документация: docs.reqdoc.ru · ключ: бот @req_doc_bot.
Спасибо, что дочитали. Критика приветствуется — за тем и пришёл.
