Public API

Справочники ОКПД-2 и КТРУ, генерация технических заданий и импорт товаров из PDF, Word и Excel — через REST API с аутентификацией по API-ключу

Быстрый старт

1

Зарегистрируйтесь

Создайте аккаунт на moy-zakupki.ru

2

Получите API-ключ

Перейдите в Профиль → API-ключи и создайте новый ключ. Ключ показывается только один раз — сохраните его.

3

Сделайте первый запрос

curl -H "Authorization: Api-Key mz_live_ваш_ключ" \
  https://moy-zakupki.ru/api/public/v2/ktru/?per_page=2

Аутентификация

Все запросы к Public API требуют заголовок Authorization с вашим API-ключом.

Формат заголовка:

Authorization: Api-Key mz_live_ваш_ключ

Base URL

https://moy-zakupki.ru/api/public/v2

Формат ответов

JSON (REST API)

Важно: API-ключ показывается только при создании. Если ключ утерян, деактивируйте его и создайте новый в настройках профиля.

КТРУ (Каталог товаров, работ, услуг)

GET/api/public/v2/ktru/

Получить список позиций КТРУ с пагинацией и фильтрацией

Query параметры:

statusСтатус КТРУ (например, "Включено в КТРУ")
is_enlargedУкрупненные позиции (true / false)
okpd2_codeФильтр по коду ОКПД-2 (например, "26.20.11")
pageНомер страницы (по умолчанию: 1)
per_pageЗаписей на странице (по умолчанию: 15, макс: 50)

Пример ответа:

{
  "count": 77000,
  "next": "https://moy-zakupki.ru/api/public/v2/ktru/?page=2",
  "previous": null,
  "results": [
    {
      "code": "01.11.11.110-00000001",
      "name": "Пшеница продовольственная",
      "shortname": "Пшеница",
      "status": "Включено в КТРУ",
      "is_enlarged": false,
      "okpd2_code": "01.11.11",
      "okpd2_name": "Выращивание зерновых культур",
      "unit_name": "тонн",
      "characteristics_count": 3
    }
  ]
}
GET/api/public/v2/ktru/{code}/

Получить детальную информацию о позиции КТРУ, включая характеристики и ограничения

Пример ответа:

{
  "code": "26.20.11.110-00000165",
  "name": "Компьютеры портативные массой не более 10 кг",
  "shortname": "Ноутбук",
  "description": "...",
  "status": "Включено в КТРУ",
  "is_enlarged": false,
  "okpd2_code": "26.20.11",
  "okpd2_name": "Компьютеры портативные",
  "unit_name": "шт",
  "characteristics": [
    {
      "key": "Диагональ экрана",
      "values": ["13.3", "15.6", "17.3"]
    }
  ],
  "limitations": [
    {
      "name": "Ограничение по цене",
      "group": "price"
    }
  ],
  "last_version": {
    "version": 3,
    "publish_date": "2024-10-15",
    "application_date_start": "2024-11-01",
    "inclusion_date": "2024-10-15"
  }
}
POST/api/public/v2/ktru/search/

Поиск КТРУ по характеристикам

Request body:

{
  "characteristics": ["мощность", "размер диагонали:27"],
  "status": "Включено в КТРУ",
  "is_enlarged": false
}

Поля status и is_enlarged необязательные. Характеристики можно передавать как ключевые слова или пары "ключ:значение".

ОКПД-2 (Классификатор)

GET/api/public/v2/okpd2/

Получить список кодов ОКПД-2 с пагинацией и фильтрацией

Query параметры:

levelУровень иерархии (2, 3, 4, 5...)
is_actualТолько актуальные (true по умолчанию)
pageНомер страницы (по умолчанию: 1)
per_pageЗаписей на странице (по умолчанию: 15, макс: 50)

Пример ответа:

{
  "count": 20372,
  "next": "https://moy-zakupki.ru/api/public/v2/okpd2/?page=2",
  "previous": null,
  "results": [
    {
      "code": "01.11.11",
      "name": "Выращивание зерновых культур",
      "level": 5,
      "parent_code": "01.11",
      "is_actual": true
    }
  ]
}
GET/api/public/v2/okpd2/{code}/

Получить детальную информацию о коде ОКПД-2, включая дочерние коды и ограничения

Пример ответа:

{
  "code": "01.11",
  "name": "Выращивание зерновых и зернобобовых культур",
  "level": 4,
  "parent_code": "01",
  "is_actual": true,
  "children": [
    {
      "code": "01.11.11",
      "name": "Выращивание зерновых культур",
      "level": 5
    }
  ],
  "limitations": [
    {
      "name": "Ограничение",
      "group": "regulatory"
    }
  ],
  "ktru_count": 42
}

Мои технические задания

Выгрузка ТЗ, созданных в вашем аккаунте. Ключ видит только ваши документы. Эти запросы не расходуют месячный лимит — он отведён под справочники.

GET/api/public/v2/tech-specs/

Список ваших ТЗ без позиций — быстрый, подходит для регулярной синхронизации.

Query параметры:

statusready / draft / generating / error
updated_afterISO-8601. Для инкрементальной выгрузки: забирайте только изменённое
created_afterISO-8601, дата или дата-время
created_beforeISO-8601, дата или дата-время
GET/api/public/v2/tech-specs/{id}/

Полная структура: позиции, коды ОКПД-2/КТРУ и характеристики.

Пример ответа:

{
  "id": "3f1c…",
  "name": "Закупка лестниц",
  "status": "ready",
  "products_count": 2,
  "products": [
    {
      "position": 1,
      "name": "Лестница алюминиевая трёхсекционная",
      "quantity": "5.00",
      "unit": "шт",
      "is_included": true,
      "ktru_code": "25.99.29.190-00000123",
      "okpd2_code": "25.99.29.190",
      "characteristics": [
        {
          "name": "Объем встроенного накопителя",
          "unit": "Гигабайт",
          "source": "ktru",
          "is_included": true,
          "values": [
            {
              "value": "512",
              "display": "≥ 512",
              "requirement_type": "range",
              "range_type": "greater_or_equal",
              "extra_value": null,
              "extra_range_type": null
            }
          ]
        }
      ]
    }
  ]
}

О значениях характеристик. Требование описывается всей связкой requirement_type / range_type / extra_value, а не одним value: например «не менее 512» — это range с greater_or_equal. Ориентируйтесь на них, если строите документ программно.

Коды могут быть null. Часть позиций не сопоставлена ни с КТРУ, ни с ОКПД-2 — это штатная ситуация, а не ошибка выгрузки. Отключённые позиции и характеристики возвращаются с is_included: false, а не пропускаются.

Генерация ТЗ

Запуск генерации технического задания — та же операция, что кнопка в личном кабинете, с тем же телом запроса. Работает асинхронно: запрос ставит задачу в очередь и сразу возвращает её идентификатор.

Генерации не расходуют лимит в 1 000 запросов/мес. Тот лимит отведён под справочники КТРУ и ОКПД-2. Генерация списывается из квоты генераций вашего тарифа — той же, что тратит интерфейс. Запуск, опрос статуса и выгрузка готового ТЗ месячный лимит запросов не расходуют вовсе: работа со своими ТЗ ничем не ограничена, кроме общего лимита запросов в минуту.

POST/api/public/v2/tech-spec-generations/

Ставит генерацию в очередь. Списывает одну генерацию за каждую позицию в items.

Тело запроса:

{
  "items": [
    {
      "product_name": "Ноутбук офисный",
      "product_code": "26.20.13.000-00000002",
      "quantity": 5,
      "unit": "шт"
    }
  ]
}

Обязательно только product_name. Остальное необязательно: product_code — код КТРУ или ОКПД2 (классификатор определяется самим кодом: у позиции КТРУ есть номер через дефис), max_resources (1–20, по умолчанию 5) и max_sites (1–10, по умолчанию 3) управляют широтой поиска источников. Блок settings с header и footer задаёт шапку и подвал документа.

Ответ 201:

{
  "id": "8c2e…",
  "status": "processing",
  "progress": 0,
  "is_finished": false,
  "product_name": "Ноутбук офисный",
  "tech_spec_id": "3f1c…",
  "error": null,
  "generation_quota_remaining": 47
}
GET/api/public/v2/tech-spec-generations/{id}/

Статус запуска. Опрашивайте раз в 10 секунд, пока is_finished не станет true: бесплатный ключ даёт 10 запросов в минуту на всё, и частый опрос упрётся в 429. Генерация занимает около полутора минут, в тяжёлых случаях — до шести.

pendingв очереди
processingгенерируется, progress растёт от 0 до 100
completedготово — забирайте документ по tech_spec_id
failedпричина в поле error, квота возвращена

Список всех ваших запусков — GET /api/public/v2/tech-spec-generations/.

Полный цикл

import time, requests

H = {"Authorization": "Api-Key mz_live_…"}
BASE = "https://moy-zakupki.ru/api/public/v2"

def is_quota_refusal(r):
    """429 с полем error — кончилась квота: ждать бесполезно."""
    try:
        return "error" in r.json()
    except ValueError:  # 429 от прокси приходит HTML-страницей
        return False

def call(method, path, **kwargs):
    """Запрос с повтором, пока действует лимит запросов в минуту."""
    while True:
        r = requests.request(method, f"{BASE}{path}", headers=H, timeout=60, **kwargs)
        if r.status_code != 429 or is_quota_refusal(r):
            return r
        time.sleep(int(r.headers.get("Retry-After", 10)))

# 1. Запускаем генерацию
r = call("POST", "/tech-spec-generations/", json={
    "items": [{"product_name": "Ноутбук офисный", "quantity": 5, "unit": "шт"}]
})
if r.status_code == 429:  # insufficient_generation_quota
    raise SystemExit(r.json()["detail"])
r.raise_for_status()  # 400, 401 и прочие отказы
task = r.json()

# 2. Ждём завершения — не чаще раза в 10 секунд
while not task["is_finished"]:
    time.sleep(10)
    r = call("GET", f"/tech-spec-generations/{task['id']}/")
    r.raise_for_status()
    task = r.json()

if task["status"] == "failed":
    raise RuntimeError(task["error"])

# 3. Забираем готовое ТЗ
r = call("GET", f"/tech-specs/{task['tech_spec_id']}/")
r.raise_for_status()
spec = r.json()
print(spec["name"], spec["products_count"])

Кончились генерации — придёт 429 с полем error: "insufficient_generation_quota". Этим он отличается от 429 при превышении лимита запросов в минуту: там такого поля нет. В первом случае нужно пополнить квоту, во втором — просто подождать.

Завершение не гарантирует полноту. Иногда пайплайн отрабатывает, но по части позиций характеристики найти не удаётся. Если результат важен для дальнейшей автоматики, проверяйте содержимое ТЗ, а не только status.

Импорт товаров

Загрузите перечень товаров — PDF, Word, Excel или просто текст, — и платформа распознает товары: наименование, код КТРУ или ОКПД2 (если он указан в документе, он проверяется и привязывается к справочнику), единицу измерения, количество и характеристики. Товары сохраняются в вашем аккаунте, а вся загрузка — отдельным ТЗ «Импорт: <имя файла>» или, если передать tech_spec_id, — в конец вашего ТЗ. Это та же операция, что кнопка «Загрузить товары из файла» в личном кабинете. Работает асинхронно: запрос сразу возвращает идентификатор загрузки, результат забирается опросом.

У импорта своя квота — загрузки. Без подписки — 5 загрузок в месяц и до 10 страниц в одной загрузке, с подпиской — 50 загрузок и до 50 страниц. Файл — до 20 МБ. Страница у PDF — настоящая страница, у Word, Excel и текста — 1 800 символов. Квота общая с личным кабинетом и считается на аккаунт, а не на ключ; обновляется 1-го числа по Москве. Лимит в 1 000 запросов к справочникам импорт не расходует — ни загрузка, ни опрос. Сбой, отказ и документ без товаров возвращают загрузку в квоту.

POST/api/public/v2/product-imports/

Принимает документ и ставит распознавание в очередь. Передайте что-то одно: файл или текст.

filemultipart/form-data: PDF, Word (.docx) или Excel (.xlsx), до 20 МБ. Формат определяется по содержимому, а не по расширению
textapplication/json: перечень товаров текстом. Содержимое .txt-файла передавайте здесь
tech_spec_idНеобязательно, поле формы или ключ JSON: id вашего ТЗ. Товары допишутся в его конец, а не в новое ТЗ «Импорт: …»; статус ТЗ не меняется. Чужое или удалённое ТЗ — 404, ТЗ, которое ещё генерируется, — 409, загрузка не списывается. Если ТЗ удалят, пока идёт разбор, товары сохранятся в новом ТЗ: tech_spec_id ответа будет отличаться от target_tech_spec_id, причина — в warnings
Idempotency-KeyЗаголовок, необязательный, до 64 символов — см. «Идемпотентность» ниже
# Файл
curl -X POST "https://moy-zakupki.ru/api/public/v2/product-imports/" \
  -H "Authorization: Api-Key mz_live_ваш_ключ" \
  -H "Idempotency-Key: spec-2026-09-27-01" \
  -F file=@spec.pdf

# Текст
curl -X POST "https://moy-zakupki.ru/api/public/v2/product-imports/" \
  -H "Authorization: Api-Key mz_live_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"text": "1. Ноутбук — 5 шт. Диагональ не менее 15,6 дюйма, ОЗУ не менее 16 ГБ\n2. Мышь беспроводная — 5 шт."}'

# Дописать в конец своего ТЗ
curl -X POST "https://moy-zakupki.ru/api/public/v2/product-imports/" \
  -H "Authorization: Api-Key mz_live_ваш_ключ" \
  -F file=@spec.xlsx \
  -F tech_spec_id=3f1c2b7a-5d4e-4f60-9a8b-7c6d5e4f3a21

Не принимаются .doc и .xls (пересохраните в .docx, .xlsx или PDF), файлы с макросами и защищённые паролем. Сканы в PDF распознаются; Word-файл, где вместо текста картинки, — нет: загрузите его как PDF.

Ответ 201 с заголовком Location:

{
  "id": "5b0e2c7a-…",
  "status": "pending",
  "is_finished": false,
  "stage": "",
  "progress": 0,
  "source": {
    "type": "pdf",
    "filename": "spec.pdf",
    "size_bytes": 184320,
    "pages": 4,
    "chars": null,
    "is_scan": false
  },
  "products_count": 0,
  "products_extracted": 0,
  "codes": {"linked": 0, "not_found": 0, "not_in_document": 0},
  "tech_spec_id": null,
  "target_tech_spec_id": null,
  "error_code": null,
  "error": null,
  "warnings": [],
  "skipped_count": 0,
  "quota_refunded": false,
  "resumed_after_auth": false,
  "created_at": "2026-09-27T10:15:02.114000+00:00",
  "finished_at": null,
  "poll_after_seconds": 10,
  "import_quota": {
    "used": 1,
    "limit": 5,
    "available": 4,
    "resets_at": "2026-10-01T00:00:00+03:00",
    "pages_per_import": 10,
    "plan": "free"
  }
}

source.pages — сколько страниц засчитано. У PDF и текста они известны сразу, у Word и Excel — после разбора, поэтому до завершения там null.

GET/api/public/v2/product-imports/{id}/

Статус загрузки, после завершения — товары. Опрашивайте не чаще раза в poll_after_seconds секунд (10), пока is_finished не станет true: бесплатный ключ даёт 10 запросов в минуту на всё.

pendingв очереди
processingидёт разбор; stage: reading → extracting → linking → saving, progress — от 0 до 100
completedготово, товары — в products. Если товаров не нашлось, products пуст, error_code: "no_products_found" и загрузка возвращена
partialчасть документа не разобрана — что именно, сказано в warnings; сохранённые товары в products, загрузка засчитана
failedсбой обработки, причина — в error, загрузка возвращена
rejectedдокумент не подошёл: страниц больше лимита, файл повреждён, в Word только картинки. Код — в error_code, загрузка возвращена

Пример ответа после завершения:

{
  "id": "5b0e2c7a-…",
  "status": "completed",
  "is_finished": true,
  "stage": "done",
  "progress": 100,
  "source": {"type": "pdf", "filename": "spec.pdf", "size_bytes": 184320,
             "pages": 4, "chars": 6953, "is_scan": false},
  "products_count": 2,
  "products_extracted": 2,
  "codes": {"linked": 2, "not_found": 0, "not_in_document": 0},
  "tech_spec_id": "3f1c…",
  "error_code": null,
  "error": null,
  "warnings": [],
  "skipped_count": 0,
  "quota_refunded": false,
  "resumed_after_auth": false,
  "created_at": "2026-09-27T10:15:02.114000+00:00",
  "finished_at": "2026-09-27T10:16:31.402000+00:00",
  "poll_after_seconds": null,
  "products": [
    {
      "id": "a91d…",
      "position": 1,
      "name": "Ноутбук",
      "unit": "шт",
      "quantity": 5.0,
      "code": {
        "as_written": "26.20.11.110-00000165",
        "ktru_code": "26.20.11.110-00000165",
        "okpd2_code": "26.20.11.110",
        "match": "ktru_exact"
      },
      "characteristics": [
        {"name": "Диагональ экрана", "value": "≥ 15.6", "unit": "дюйм", "by_ktru": true},
        {"name": "Объем оперативной памяти", "value": "не менее 16", "unit": "Гигабайт", "by_ktru": true},
        {"name": "Тип накопителя", "value": "SSD", "unit": null, "by_ktru": false}
      ],
      "notes": ["Характеристик сопоставлено с КТРУ: 2 из 3."]
    },
    {
      "id": "c07b…",
      "position": 2,
      "name": "Мышь компьютерная беспроводная",
      "unit": "шт",
      "quantity": 5.0,
      "code": {
        "as_written": "26.20.16.170-00000999",
        "ktru_code": null,
        "okpd2_code": "26.20.16.170",
        "match": "ktru_not_found_okpd2_from_prefix"
      },
      "characteristics": [
        {"name": "Тип подключения", "value": "беспроводное", "unit": null, "by_ktru": false}
      ],
      "notes": ["Код КТРУ «26.20.16.170-00000999» не найден; привязан ОКПД2 26.20.16.170."]
    }
  ]
}

Как привязан код — поле code.match: ktru_exact и okpd2_exact — код из документа найден в справочнике; okpd2_ancestor — ОКПД2 не найден, привязан ближайший вышестоящий; ktru_not_found_okpd2_from_prefix — КТРУ не найден, привязан ОКПД2 из его начала; drug_registry_code — это код лекарства (ЕСКЛП), а не КТРУ; not_found и invalid_format — код не найден или не похож на код; code_not_in_document — кода нет в тексте документа, и он не привязан; no_code — в документе кода не было. Код по названию товара не подбирается. code.as_written — код дословно из документа.

Характеристики по КТРУ — by_ktru: true, если справочник КТРУ знает и имя характеристики, и её значение; в документе Word они идут как «Характеристика по КТРУ». Остальные — дополнительные.

Значения — как в документе. Характеристика — строка value с требованием в том виде, как оно записано: "≥ 27", "от 10 до 20", "белый"; unit и коды могут быть null. Под позицией в notes — пометки о том, что стоит проверить: код не найден, единица не распознана, количество заменено на 1.

GET/api/public/v2/product-imports/

Ваши загрузки, новые сверху, без товаров. Параметры page и per_page (до 50), фильтры status (например, completed) и created_after (2026-09-01 или ISO 8601 с временем); ответ — в обычной пагинации. Загрузки из личного кабинета здесь тоже видны.

GET/api/public/v2/product-imports/limits/

Форматы, лимиты тарифов и расход загрузок этого месяца. Удобно проверить до отправки большого документа.

{
  "formats": [".pdf", ".docx", ".xlsx", ".txt", "text"],
  "max_file_bytes": 20971520,
  "chars_per_page": 1800,
  "free": {"monthly_imports": 5, "pages_per_import": 10},
  "paid": {"monthly_imports": 50, "pages_per_import": 50},
  "import_quota": {
    "used": 1,
    "limit": 5,
    "available": 4,
    "resets_at": "2026-10-01T00:00:00+03:00",
    "pages_per_import": 10,
    "plan": "free"
  }
}

Ошибки загрузки

Отказ приходит в одной форме: {"error": "<код>", "detail": "<пояснение>"}. Ни один отказ не списывает загрузку.

HTTP error Когда
400 invalid_request, empty_document Не передан ни файл, ни текст (или переданы оба); файл или текст пустой
404 tech_spec_not_found ТЗ из tech_spec_id не найдено среди ваших: чужое, удалено или id некорректен
409 import_in_progress Предыдущая загрузка аккаунта ещё обрабатывается: одновременно идёт только одна
409 idempotency_key_reused Тот же Idempotency-Key уже использован для другого документа или другого tech_spec_id
409 tech_spec_generating ТЗ из tech_spec_id ещё генерируется — дождитесь окончания и повторите
413 file_too_large Файл больше 20 МБ
415 unsupported_format, legacy_format, macro_enabled, encrypted, corrupted Не PDF, DOCX или XLSX; .doc / .xls; файл с макросами; защищён паролем; повреждён
422 too_many_pages Страниц больше лимита тарифа (в теле — pages и limit_pages). Так же — повреждённый или запароленный PDF (corrupted, encrypted)
429 import_quota_exceeded Кончились загрузки месяца (reason: "monthly_limit") или сработал суточный предохранитель попыток ("daily_attempts"). В теле — import_quota и payment_url. Ждать и повторять бесполезно
500 storage_failed, dispatch_failed Не удалось сохранить файл или поставить его в очередь. Загрузка не списана — повторите

429 без поля error — это лимит запросов в минуту: подождите Retry-After секунд и повторите. У Word и Excel страницы считаются уже при разборе, поэтому лишние страницы в них приходят не 422, а статусом rejected с error_code: "too_many_pages".

Идемпотентность. Передайте заголовок Idempotency-Key — любую строку до 64 символов, одну на документ, — и повтор запроса после обрыва сети безопасен: тот же ключ с тем же телом вернёт ту же загрузку со статусом 200 и заголовком Idempotent-Replayed: true, вторая загрузка не спишется. Тот же ключ с другим документом — 409 idempotency_key_reused. Без ключа повторная отправка того же файла создаст товары ещё раз — в warnings придёт предупреждение.

Полный цикл

import time, uuid, requests

H = {"Authorization": "Api-Key mz_live_…"}
BASE = "https://moy-zakupki.ru/api/public/v2"

def is_quota_refusal(r):
    """429 с полем error — кончилась квота: ждать бесполезно."""
    try:
        return "error" in r.json()
    except ValueError:  # 429 от прокси приходит HTML-страницей
        return False

def call(method, path, headers=None, **kwargs):
    """Запрос с повтором, пока действует лимит запросов в минуту."""
    while True:
        r = requests.request(method, f"{BASE}{path}", headers={**H, **(headers or {})},
                             timeout=120, **kwargs)
        if r.status_code != 429 or is_quota_refusal(r):
            return r
        time.sleep(int(r.headers.get("Retry-After", 10)))

# 1. Загружаем файл. Ключ идемпотентности — один на документ:
#    повтор с ним после обрыва сети не спишет вторую загрузку.
with open("spec.pdf", "rb") as fh:
    data = fh.read()
key = str(uuid.uuid4())

r = call("POST", "/product-imports/", headers={"Idempotency-Key": key},
         files={"file": ("spec.pdf", data)})
if r.status_code == 429:  # import_quota_exceeded
    body = r.json()
    raise SystemExit(f"{body['detail']} Тарифы: https://moy-zakupki.ru{body['payment_url']}")
if r.status_code not in (200, 201):  # 400, 409, 413, 415, 422, 500 — не списано
    raise SystemExit(f"{r.status_code}: {r.text[:500]}")
job = r.json()

# 2. Ждём завершения — не чаще, чем просит сервер
while not job["is_finished"]:
    time.sleep(job["poll_after_seconds"] or 10)
    r = call("GET", f"/product-imports/{job['id']}/")
    r.raise_for_status()
    job = r.json()

# 3. Результат
if job["status"] in ("failed", "rejected"):  # загрузка возвращена в квоту
    raise SystemExit(f"{job['error_code']}: {job['error']}")
for warning in job["warnings"]:
    print("Внимание:", warning)
if not job["products"]:
    print(job["error"])  # «В документе не найдено товаров» — тоже не списано

for p in job["products"]:
    code = p["code"]["ktru_code"] or p["code"]["okpd2_code"] or "без кода"
    print(f"{p['position']}. {p['name']} — {p['quantity']:g} {p['unit']} [{code}]")
    for c in p["characteristics"]:
        print(f"    {c['name']}: {c['value']} {c['unit'] or ''}")
    for note in p["notes"]:
        print(f"    ! {note}")

Тарифы и лимиты

Каждый API-ключ привязан к тарифу, определяющему месячную квоту и ограничение по частоте запросов. Месячная квота расходуется только справочниками (КТРУ и ОКПД-2); работа со своими ТЗ — генерация, опрос статуса и выгрузка — и импорт товаров из неё не списываются.

Тариф Запросов / месяц Запросов / мин
Free 1 000 10
Starter 10 000 30
Business 100 000 60
Enterprise 1 000 000 120

При превышении лимита API вернет 429 Too Many Requests с заголовком Retry-After. Месячная квота сбрасывается 1-го числа каждого месяца.

Оба лимита действуют на аккаунт целиком. Ключей можно завести сколько угодно — например, по одному на приложение, — но счётчик у них общий, и выпуск нового ключа его не обнуляет. Текущие значения всегда приходят в заголовках X-RateLimit-* и X-Monthly-Quota-*.

Эта квота — про справочники. Генерация ТЗ тратит отдельную квоту генераций вашего тарифа, импорт товаров — свою квоту загрузок, а не эти запросы; опрос статуса не расходует ни одну из квот. Лимит запросов в минуту действует на всё: при 10 запросах в минуту опрашивайте статус не чаще раза в 10 секунд.

Коды ошибок

401

Unauthorized

Отсутствует или невалидный API-ключ, ключ деактивирован или истёк. Исчерпанная квота даёт не 401, а 429

400

Bad Request

Некорректные параметры запроса

404

Not Found

Не найден код КТРУ или ОКПД-2 либо ваше ТЗ, генерация или загрузка с таким идентификатором

429

Too Many Requests

Превышен лимит запросов в минуту или исчерпана месячная квота справочников — повторите через Retry-After секунд. Если в теле есть поле error, кончилась квота генераций (insufficient_generation_quota) или загрузок (import_quota_exceeded) — повтор не поможет: пополните квоту по ссылке из payment_url или дождитесь её обновления

500

Internal Server Error

Внутренняя ошибка сервера. Повторите запрос позже

Пагинация

Все списочные эндпоинты возвращают результаты с пагинацией. Для навигации по страницам используйте параметры page и per_page.

pageНомер страницы (по умолчанию: 1)
per_pageЗаписей на странице (по умолчанию: 15, максимум: 50)
GET /api/public/v2/ktru/?page=3&per_page=25

{
  "count": 77000,
  "next": "https://moy-zakupki.ru/api/public/v2/ktru/?page=4&per_page=25",
  "previous": "https://moy-zakupki.ru/api/public/v2/ktru/?page=2&per_page=25",
  "results": [...]
}

Примеры кода

Python (requests)

import requests

API_KEY = "mz_live_ваш_ключ"
BASE_URL = "https://moy-zakupki.ru/api/public/v2"
HEADERS = {"Authorization": f"Api-Key {API_KEY}"}

# Получить список КТРУ
response = requests.get(
    f"{BASE_URL}/ktru/",
    headers=HEADERS,
    params={"status": "Включено в КТРУ", "per_page": 10}
)
data = response.json()
print(f"Всего записей: {data['count']}")
for item in data["results"]:
    print(f"  {item['code']} — {item['name']}")

# Получить детали по коду
response = requests.get(
    f"{BASE_URL}/ktru/26.20.11.110-00000165/",
    headers=HEADERS
)
detail = response.json()
print(f"Характеристик: {len(detail['characteristics'])}")

JavaScript (Fetch API)

const API_KEY = "mz_live_ваш_ключ";
const BASE_URL = "https://moy-zakupki.ru/api/public/v2";

// Получить список КТРУ
const response = await fetch(
  `${BASE_URL}/ktru/?status=Включено+в+КТРУ&per_page=10`,
  { headers: { "Authorization": `Api-Key ${API_KEY}` } }
);
const data = await response.json();
console.log(`Всего записей: ${data.count}`);
data.results.forEach(item =>
  console.log(`  ${item.code} — ${item.name}`)
);

// Получить детали ОКПД-2
const okpdResponse = await fetch(
  `${BASE_URL}/okpd2/26.20.11/`,
  { headers: { "Authorization": `Api-Key ${API_KEY}` } }
);
const okpd = await okpdResponse.json();
console.log(`Дочерних кодов: ${okpd.children.length}`);

cURL

# Список КТРУ
curl "https://moy-zakupki.ru/api/public/v2/ktru/?per_page=5" \
  -H "Authorization: Api-Key mz_live_ваш_ключ"

# Детали КТРУ по коду
curl "https://moy-zakupki.ru/api/public/v2/ktru/26.20.11.110-00000165/" \
  -H "Authorization: Api-Key mz_live_ваш_ключ"

# Список ОКПД-2 (уровень 3)
curl "https://moy-zakupki.ru/api/public/v2/okpd2/?level=3&per_page=10" \
  -H "Authorization: Api-Key mz_live_ваш_ключ"

# Поиск КТРУ по характеристикам
curl -X POST "https://moy-zakupki.ru/api/public/v2/ktru/search/" \
  -H "Authorization: Api-Key mz_live_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"characteristics": ["мощность", "напряжение:220"]}'

Интерактивная документация

Полная интерактивная документация доступна через Swagger UI. Можно выполнять запросы прямо из браузера.

Открыть Swagger UI