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}/protocolPDF протокола по завершённой закупке — в пути передаётся номер закупки. См. §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_dateYYYY-MM-DD. Это один день, а не диапазон — читайте §1.3.
company_id12-значный БИН заказчика. Применяется при любом значении: короткий или неверный БИН вернёт 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=506–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-обменом.

Два входа:

  1. ЭЦП — родной путь портала. Три вызова (§2.1).
  2. SSO через zakup.gov.kzhttps://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, доказывает всю цепочку.