Plank help · updated 2026-09-09
Работа с порталом Tizilim (закупки недропользователей)
Как читать и автоматизировать tizilim.gov.kz — государственный реестр товаров, работ и услуг для операций по недропользованию. Документированного API нет, но публичный API на чтение работает вообще без учётных данных, а сам портал живёт на REST API, которым можно управлять, если вы умеете подписывать ЭЦП.
Agents: fetch the raw markdown of this page at /ru/help/tizilim-portal.md
Работа с порталом Tizilim (закупки недропользователей)
tizilim.gov.kz — государственная информационная система «Реестр товаров, работ и услуг, используемых при проведении операций по недропользованию, и их производителей». С 1 июля 2025 года все объявления о закупках недропользователей публикуются только там — портал полностью заменил reestr.nadloc.kz.
Он интересен двум типам клиентов:
- Поставщикам — чтобы узнавать, не заходя каждый день на сайт, какие лоты подходят под то, что они продают.
- Недропользователям (заказчикам) — они публикуют годовые планы, проводят закупки и сдают отчётность по казахстанскому содержанию. Эта работа однообразная, со сроками, и делается руками.
Документированного API и программы для разработчиков нет. На страницах «Часто задаваемые вопросы», «Инструкции» и «Полезные ссылки» слова «API» и «интеграция» не встречаются ни разу. Всё описанное ниже прочитано из клиентского бандла самого портала и проверено на живой системе 09.09.2026. Это недокументированный интерфейс: он может измениться без предупреждения, поэтому код должен падать громко, а не молча возвращать пустоту.
Поверхностей две. Начинайте с первой — вторая нужна далеко не всегда.
1. Публичный API на чтение — без учётных данных, работает уже сейчас
Публичный портал (public.tizilim.gov.kz) — это клиентское приложение, которое ходит в обычный JSON API. В этот API можно ходить напрямую: без авторизации, без ЭЦП и без браузера.
База: https://api.tizilim.gov.kz (то же самое отдаётся и на https://public.tizilim.gov.kz; ссылки пагинации в ответе указывают на хост api.).
| Эндпоинт | Что возвращает |
|---|---|
GET /api/public/tenders | Закупки. С пагинацией. |
GET /api/public/lots | Отдельные лоты. С пагинацией. |
GET /api/public/stats | Итоги по порталу — закупки, завершённые закупки, пункты плана. |
GET /api/public/news | Новости. |
GET /api/public/pages/{slug} | Статические страницы: documents (инструкции), laws, faq, about, contact, useful-links. |
GET /api/public/refs/auction-types | Способы закупки. |
GET /api/public/refs/auction-statuses | Статусы закупок. |
GET /api/public/refs/lot-statuses | Статусы лотов. |
GET /api/public/refs/tru-types | Типы ТРУ. |
GET /api/public/tenders/{number}/protocol | PDF протокола по завершённой закупке — в пути передаётся номер закупки. См. §1.4. |
1.1 Фильтры
/tenders и /lots принимают одинаковый набор параметров (проверено на обоих):
| Параметр | Значение |
|---|---|
page, per_page | Пагинация Laravel. |
search | Подстрока, регистронезависимо, по названию и по номеру. |
status[] | Повторяемый. Коды из refs/auction-statuses (закупки) или refs/lot-statuses (лоты). |
auction_type[] | Повторяемый. Коды из refs/auction-types. |
enstru_type[] | Повторяемый. 0 — товары, 1 — работы, 2 — услуги. |
start_date, end_date | YYYY-MM-DD. Это один день, а не диапазон — читайте §1.3. |
company_id | 12-значный БИН заказчика. Применяется при любом значении: короткий или неверный БИН вернёт 0 строк, а не всё подряд, поэтому опечатка читается как «у этого заказчика нет закупок». Интерфейс портала отправляет его только при длине ровно 12; на сервере такой проверки нет. |
Справочные значения на 09.09.2026:
auction-types 101 Открытый конкурс · 103 Из одного источника
104 на товарных биржах · 105 Открытый конкурс на понижение
112 без применения способов
auction-statuses PUBLISHED · BIDDING · SUPPLIER_OFFERS · TRADES_PUBLISHED
CANCELED · REJECTED · REFUSE · WAITING_SIGN
WAITING_RESULT_SIGN · COMPLETED
tru-types 0 товары · 1 работы · 2 услуги
Читайте их из refs/ в рантайме, а не зашивайте эту таблицу: это снимок, а запрос бесплатный.
1.2 Он медленный. Проектируйте с учётом этого.
Замеры с сервера в Европе:
per_page=50→ 6–13 с на страницу, от запуска к запуску по-разному.per_page=200→ работает, ~21 с. Пропускная способность в пересчёте на строку та же, что и при 50, так что большая страница ничего не даёт; ранее здесь стоял «таймаут» — это был наш собственный дедлайн клиента в 25 с, а не отсечка сервера./refs/*→ заметно меньше секунды./stats→ доли секунды на прогретом, ~1,7 с на холодном.
Отсюда: берите per_page=50 и таймаут клиента 60 с — не потому, что страницы побольше падают, а потому, что они ничего не дают и падают дольше. Не выкачивайте весь корпус (707 страниц ≈ 1,5–2,5 часа), если задачу решает фильтр. Опубликованных лимитов частоты нет, robots.txt нет — всё равно будьте аккуратны: один последовательный запрос за раз, без параллельного веера.
1.3 Две ловушки, из-за которых ответы будут неправильными
start_date — это равенство по дню, а не нижняя граница. start_date=2026-09-01 вернёт 88 закупок, опубликованных именно в этот день, а не всё с этой даты. Пара start_date=2026-01-01&end_date=2026-01-31 вернёт 0, потому что это значит «началась 1 января и закончилась 31 января». Чтобы покрыть период — идите по дням.
Сортировка по умолчанию не «сначала новые» и недостаточно стабильна, чтобы по ней считать разницу. Первая страница начинается с самых больших номеров, но уже в ней попадается более старый; на последней странице (сегодня 707-й, и это число плывёт) порядка нет вовсе. Поэтому не делайте «что нового с прошлого раза» как «прочитать первую страницу и сравнить». Опрашивайте по дням:
import datetime, requests
BASE = "https://api.tizilim.gov.kz/api/public"
def tenders_for_day(day: datetime.date, **filters) -> list[dict]:
"""Все закупки, опубликованные за один день. Детерминированно, перезапуск безопасен."""
out, page = [], 1
while True:
r = requests.get(f"{BASE}/tenders", timeout=60, params={
"start_date": day.isoformat(), "page": page, "per_page": 50, **filters,
})
r.raise_for_status()
body = r.json()
out.extend(body["data"])
if page >= body["meta"]["last_page"]:
return out
page += 1
# Вчерашние закупки по категории «работы»:
rows = tenders_for_day(datetime.date.today() - datetime.timedelta(days=1),
**{"enstru_type[]": "1"})
Ежедневное наблюдение тогда выглядит так: за каждый день с последнего запуска выгрузить день, отфильтровать локально по тому, что реально нужно клиенту (ключевые слова, префикс кода ЕНС ТРУ, БИН, сумма), и отчитаться. Храните уже отправленные number, чтобы перезапуск был идемпотентным.
1.4 Чего публичный API не даёт
- Нет связи лот→закупка, нет документов, нет списка участников, нет карточки лота глубже строки списка.
- В строках лотов нет дат.
/lotsпринимает и учитываетstart_date, но в возвращаемой строке нет полейstart_date/end_date, так что значение, по которому вы фильтровали, увидеть нельзя. - В строке нет БИН заказчика — только
customer.name_ru. Отфильтровать поcompany_idможно, если клиент сам назвал БИН, но прочитать БИН обратно нельзя. - Формы у двух эндпоинтов разные. У закупки
status— объект ({name_ru, name_en, name_kz}), у лотаstatus— просто строка.name_enчастоnullу обоих. Не считайте ни одно поле обязательным.
Проверенные формы строк:
закупка number name_ru name_en name_kz customer{name_*} lots_count
offers_count amount type{name_*} status{name_*} start_date end_date
лот number name_* code description_* quantity amount type{name_*} status(строка)
code у лота — классификатор ЕНС ТРУ (например 422121.100.000000). Это правильный ключ для «всё по моей категории», гораздо лучше поиска по словам. number у лота — целое число, у закупки — строка вида 2026.ОК-38085; не считайте, что тип один и тот же.
1.5 PDF протокола — доступен, по номеру
GET /api/public/tenders/{number}/protocol принимает номер закупки из строки списка и отдаёт настоящий PDF:
2026.ОИ-38204 → 200 application/pdf, ~50 КБ
2026.ОК-38085 → 404 {"message":"Протокол не найден"} закупка есть, протокола ещё нет
2026.ОК-99999 → 404 {"message":"Закупка не найдена"} такого номера нет
38085 → 404 {"message":"Закупка не найдена"} числовой id — НЕ ключ
Различайте эти два 404 прежде, чем делать выводы. «Протокол не найден» значит, что вы спросили рано — закупка ещё идёт. «Закупка не найдена» значит, что ключ неверный, и обычная причина — передали числовой id вместо номера. Ранняя версия этой страницы сделала ровно эту ошибку и заключила, что эндпоинт непригоден.
2. API с авторизацией — весь портал, если умеете подписывать
tizilim.gov.kz — одностраничное приложение на Nuxt поверх REST-бэкенда на Laravel по адресу https://tizilim.gov.kz/api/. Любое действие пользователя в интерфейсе — это один вызов этого API. В браузере нельзя сделать ничего, чего не может скрипт.
Авторизация: токен Bearer со сроком 24 ч, на клиенте лежит в куке auth.token. CSRF-токен берётся из GET /api/csrf-token перед SSO-обменом.
Два входа:
- ЭЦП — родной путь портала. Три вызова (§2.1).
- SSO через zakup.gov.kz —
https://zakup.gov.kz/api/sso/connect/authorize?client_id=tizilim&scope=api offline_access&response_type=code&redirect_uri=…, обычный OpenID Connect с кодом, колбэк уходит вPOST /api/auth/sso-callback. Это переносит задачу ЭЦП на другой портал, а не убирает её; предпочитайте путь 1.
Входа только по паролю нет. /auth/login-esp принимает логин и пароль, но лишь вместе с подписанным XML.
2.1 Цепочка входа
POST /api/auth/esign-auth-xml → XML, который нужно подписать (авторизация не нужна)
POST /api/auth/login-check-esp {xml: <подписанный>} → доступные роли
POST /api/auth/login-esp {xml, login, password, type} → {access_token}
XML, который отдаёт сервер, тривиален — это просто одноразовая метка. Вопреки названию поля, в uuid сервер подставляет IP-адрес самого вызывающего, так что у вас будет другой:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<key_settings_digi_sign>
<uuid>203.0.113.10</uuid>
<date>2026-09-09 11:29:45</date>
</key_settings_digi_sign>
Подпишите его как enveloped XMLDSig ключом ЭЦП пользователя и отправьте обратно. Как именно — в Подпись ключом ЭЦП РК: это тот же механизм, что уже используется в интеграции с ЭСФ, и NCALayer для него не нужен.
Подпись не принята? Эндпоинт отвечает 422 {"success":false,"error":"Ошибка при обработке сертификата"}. Одно и то же сообщение приходит и на кривую подпись, и на неверный тип ключа, и на недоверенную цепочку — поэтому меняйте по одному параметру за раз.
2.2 Семейства эндпоинтов
Около 276 эндпоинтов, все под /api/, сгруппированы так же, как разделы интерфейса:
| Семейство | Что там |
|---|---|
/plan/plan-point/* | Годовой план — включая import, export, export-example, sign, sign-empty, revert, delete-all. |
/tender/open-tender/*, /tender/open-tender-reduction/*, /tender/single-source/*, /tender/offtake/* | Создание, редактирование и публикация закупки по способу: store, {id}/update, {id}/publish, {id}/cancel, {id}/refuse. |
/tender/{id}/protocol/* | Вскрытие, рассмотрение заявок, итоги — у каждого есть двойник с -sign. |
/tender/{id}/offers/* | Заявки поставщиков: оценка, сравнение, отклонение, архивирование, победители. |
/my/application*, /my/applications | Сторона поставщика — подача, уточнение и отзыв заявки. |
/agreement/* | Договоры: send-to-sign, sign, terminate, not-conclude. |
/reports/*, /report/* | Отчётность по казахстанскому содержанию (tpi / uvs), с import, export и sign. |
/company/* | Профиль, сотрудники, пользователи, комиссия, договоры и keys (это не ключ ЭЦП — см. §3). |
/reestr/*, /refs/*, /notification/* | Чтение реестра, справочники, уведомления. |
Самая ценная автоматизация — в скучной части этого списка: /plan/plan-point/import, /plan/plan-point/export и /reports/submitting/tpi/{id}/import уже работают с файлами. Клиент, который руками набивает годовой план или квартальный отчёт по КС, делает работу, которую эти три эндпоинта делают одним вызовом.
2.3 Каждая запись — отдельная подпись
Схема записи на портале одинаковая и совпадает со схемой входа:
POST <действие>-xml → неподписанный XML, собранный сервером из ожидающего действия
подписать локально
POST <действие> {xml: <подписанный>, …} → зафиксировано
Примеры первой половины: /plan/plan-point/sign-xml, /plan/plan-point/sign-empty-xml, /auth/esign-auth-xml, /auth/password/reset-xml, а также пути вида /tender/{id}/protocol/result-sign.
Что из этого следует — и что надо сказать клиенту до начала работ:
- Подпись ставится на каждое действие, а не на сессию. Опубликовать 40 пунктов плана — это 40 подписей. Для скрипта это нормально, руками — невозможно; именно поэтому автоматизацию имеет смысл делать.
- Ключ ЭЦП должен быть доступен весь прогон, а не только на входе.
- Никогда не подписывайте в цикле без утверждённого плана. Соберите всё, покажите пользователю, что будет опубликовано, получите явное «да» — и только потом подписывайте и отправляйте. Опубликованная закупка видна всем поставщикам страны.
3. Ключей два. Не перепутайте.
| Ключ ЭЦП | Пара из /company/keys | |
|---|---|---|
| Что это | .p12 от НУЦ РК + пароль | RSA-OAEP-4096, генерируется в браузере |
| Для чего | Вход и каждое подписанное действие | Шифрование цен в заявках |
| Откуда берётся | Уже есть у пользователя | POST /api/company/keys отдаёт её один раз; приватный PEM скачивается на диск пользователя, и портал его больше не видит |
| Если его нет | Не войти | Не вскрыть заявки — закупка встаёт на этапе вскрытия |
Вторая — настоящая ловушка: она создаётся на компанию через /company/keys/allow-create, отзывается через /company/keys/{id}/deactivate, а приватная половина существует только как файл, который пользователь когда-то скачал — возможно, месяцы назад, возможно, на машину, которой уже нет. Если автоматизация доходит до вскрытия заявок, спрашивайте этот PEM заранее: обнаружить его пропажу в момент вскрытия — это уже не чинится.
4. Не надо управлять браузером
Соблазн автоматизировать интерфейс, «потому что там нужен NCALayer», строится на двух неверных посылках:
- Портал — чистый клиентский SPA поверх того же REST API: HTML-оболочка весит 4 КБ и не содержит данных. Chrome не даст вам ничего сверх API, зато добавит нестабильный DOM.
- NCALayer — десктопное Java-приложение на машине пользователя. Браузер в песочнице не достучится до
wss://127.0.0.1:13579, так что автоматизация браузера задачу подписи не решает, а наследует.
Ходите в API. Подписывайте в процессе. См. Подпись ключом ЭЦП РК.
5. Прежде чем строить на этом
- Ничего из перечисленного не является договорённостью. Ни документации, ни условий использования, ни версионирования. Недокументированный эндпоинт может поменять форму за ночь; ваш код должен падать, а не пожимать плечами.
- Спросите портал. Для чего-либо сверх вежливого темпа чтения напишите на
tizilim@qazindustry.gov.kz(у сайта есть и телеграм-бот поддержки@iDos_prime_bot). Они сами заявляют интеграцию со справочником ЕНС ТРУ, так что официальный канал может существовать и просто не публиковаться — и получить ответ письменно стоит до того, как от этого зависит клиент. - Реальный блокер для записи — учётные данные, а не технология. Для записи нужен ключ ЭЦП клиента там, где до него дотянется скрипт. Обращайтесь с ним ровно так, как это описано в Подключении ЭСФ: защищённые учётные данные рабочего пространства,
.gitignoreна*.p12, никогда в чат и в логи, понимание того, кто ещё видит файл в общем рабочем пространстве (Скрипты и интеграции), и явное предложение оставить ключ на машине пользователя.
6. Что на самом деле проверено
Честное состояние на 09.09.2026, чтобы никто не выводил это заново:
- ✅ Проверено на живой системе: все публичные эндпоинты из §1, все справочные значения, тайминги, ловушки с сортировкой и
start_date, все три ответа протокола из §1.5. - ✅ Проверено на живой системе, но с пробелом: фильтры.
search,status[],auction_type[],enstru_type[],start_dateиend_dateпрогнаны на реальных результатах. Проcompany_idпоказано только то, что он возвращает 0 на значениях, которым не соответствует ни одна компания, — корректный БИН как положительный контроль не использовался ни разу, так что «фильтрует по БИН» прочитано из клиентского кода портала, а не измерено. - ⚠️ Прочитано из клиентского бандла, но не выполнялось: описание
/company/keysиз §3 (RSA-OAEP-4096, генерация в браузере, разовая выгрузка PEM). - ✅ Проверено на живой системе:
POST /api/auth/esign-auth-xmlотдаёт XML без авторизации, аPOST /api/auth/login-check-espс мусором отвечает422«Ошибка при обработке сертификата». - ⚠️ Прочитано из клиентского бандла, но не выполнялось: список эндпоинтов из §2.2, тела запросов и срок жизни токена
Bearer. - ❌ Не проверено — этого ещё никто не делал: подписанный круг. Никто пока не сделал подпись, которую этот портал принял. Это единственный эксперимент, который надо провести до обещаний про автоматизацию записи, и он дешёвый: один
esign-auth-xml→ подпись →login-check-esp, который вернул список ролей вместо 422, доказывает всю цепочку.