API

Отправляйте свой прайс в ADAM

Один HTTP-запрос заменяет таблицу. Ваша система отправляет выгрузку, а мы строка за строкой говорим, что прочитали и чего не смогли.

Проверить payload, ничего не создавая, можно в Настройки → Интеграции → Песочница. Документ OpenAPI · Скачать в Markdown — для ИИ-агентов

Как получить ключ

Настройки → Интеграции → Подключить. Секрет показывается один раз при создании и хранится только как хеш — держите его там, где его читает ваша ERP. Отправляйте как Authorization: Bearer ….

curl -X POST https://ingest.adam.ua/v1/products \
  -H "Authorization: Bearer adam_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"sku":"KRB-46","name":"Карбамід 46% гранульований","brand":"Sumykhimprom","category":"Добрива","unit":"кг","price":12.5,"stock":1000,"warehouse":"Київський склад","moq":100,"updated_at":"2026-08-24T14:02:31Z"}]}'

Позиция, поле за полем

ПолеТипПравило
namestring · ≤300
обязательное
Название товара в вашей системе. Обрезается, не может быть пустым.
brandstring · ≤120
обязательное
Распознаётся матчером внутри категории.
categorystring · ≤120
обязательное
В человеческом виде. Распознаётся по списку ниже.
unitstring · ≤40
обязательное
В человеческом виде: «шт», «шт.», «кг», «т», «канистра» — все распознаются.
pricenumber
обязательное
Больше 0. Число, не строка; десятичный разделитель — «.».
stocknumber
обязательное
0 или больше. Ноль означает «нет в наличии», а не «неизвестно».
skustring · ≤100
необязательное
Ваш артикул. По схеме необязателен, но без него последующий PATCH не имеет по чему искать позицию.
warehousestring · ≤200
необязательное
Сверяется с вашими складами — сначала точное название или город, затем единственный склад, название которого содержит это значение. Если не совпало или значения нет вовсе, строка ляжет на ваш склад по умолчанию: единственный ваш склад либо тот, который вы отметили основным. Два кандидата — единственный случай, когда мы не угадываем: склад не проставляется, возвращается `warehouse_ambiguous`.
moqnumber
необязательное
Больше 0. По умолчанию 1.
descriptionstring · ≤500
необязательное
Свободный текст. Используется, когда строка становится заявкой на новый товар каталога.
updated_atrfc3339
необязательное
Когда позиция последний раз менялась в вашей системе. Даёт правило порядка для PATCH.

Поле, которое мы не читаем, ломает весь запрос, а не отбрасывается тихо. Тихое отбрасывание — это то, как вы начинаете верить, что задали валюту, которой не задавали.

Ответ

202, потому что строки приняты в обработку, а сопоставление и проверка ещё впереди. Отклонения называют ваш собственный индекс позиции, поэтому выгрузка, которую вы правите поэтапно, сходится.

{
  "request_id": "8f14e45f-ceea-467a-9c4c-1b0f0e5c9e2a",
  "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "accepted": 1198,
  "rejected": [
    {
      "index": 42,
      "sku": "XYZ-1",
      "reason": "category_unresolved",
      "detail": "«Хімія» matches no category"
    },
    {
      "index": 87,
      "sku": "KRB-99",
      "reason": "required_field_missing",
      "field": "brand"
    }
  ],
  "rejected_total": 2
}

Коды причин

КодОбластьЗначение
invalid_envelopeвесь запрос

Тело запроса — не объект с массивом items.

unknown_fieldвесь запрос

Поле, которое мы не читаем. Возвращаем ошибку, а не молча отбрасываем — чтобы вы не считали, что задали то, чего не задали.

idempotency_key_reusedвесь запрос

Этот Idempotency-Key уже использован для запроса с другим телом. Возьмите новый ключ — сохранённый результат принадлежит предыдущему запросу, а не этому.

no_itemsвесь запрос

items пуст.

too_many_itemsвесь запрос

Позиций больше лимита на запрос. Отправьте меньшими партиями.

required_field_missingодна строка

Содержит field — обязательное поле, которого нет или оно пустое. На PATCH /v1/offers это всегда sku.

field_too_longодна строка

Содержит field. Граница для каждого поля — в таблице выше.

price_invalidодна строка

Не число или не больше 0.

stock_invalidодна строка

Не число или отрицательное. Ноль допустим.

moq_invalidодна строка

Не число или не больше 0.

sku_duplicate_in_payloadодна строка

Тот же артикул уже есть раньше в этом запросе. Побеждает первое вхождение.

updated_at_invalidодна строка

Не RFC 3339, например 2026-08-24T14:02:31Z.

review_backlogвесь запрос

Ваши непроверенные строки достигли лимита. Завершите проверку, прежде чем отправлять ещё.

category_unresolvedодна строка

Значение не совпадает ни со slug, ни с псевдонимом.

unit_unresolvedодна строка

Значение не совпадает ни с одной единицей.

wrong_endpointодна строка

Вы отправили discontinued сюда. Снятие с продажи и возврат — это PATCH /v1/offers.

Закрытый набор — ваша интеграция может на них ветвиться. Один код на отклонённую строку: строка, не прошедшая несколько проверок, сообщает первую по порядку выше.

Обновление цены и остатка

Когда позиция связана, PATCH /v1/offers меняет её цену и остаток без проверки и без участия человека. Вы отправляете тот же артикул, что уже отправляли в выгрузку товаров, а мы меняем предложение за ним. Это тот вызов, который ваша ERP делает по расписанию.

curl -X PATCH https://ingest.adam.ua/v1/offers \
  -H "Authorization: Bearer adam_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"sku":"KRB-46","price":12.9,"stock":840,"updated_at":"2026-08-25T09:00:00Z"},{"sku":"KAS-32","stock":0},{"sku":"OLD-01","discontinued":true}]}'
ПолеТипПравило
skustring · ≤100
обязательное
Ваш артикул, тот же, что отправляли в POST /v1/products. Единственный способ назвать позицию.
pricenumber
необязательное
Больше 0. Не отправляйте — цена останется как есть.
stocknumber
необязательное
0 или больше. Ноль ставит «нет в наличии» и оставляет предложение в каталоге.
moqnumber
необязательное
Больше 0. Минимальная партия; единица измерения здесь не меняется.
discontinuedboolean
необязательное
true снимает предложение с продажи, false возвращает. Снятие всегда проходит; возврат может быть отклонён.
updated_atrfc3339
необязательное
Когда позиция последний раз менялась в вашей системе. Отправьте — и запоздавший повтор не перезапишет более новую цену; не отправите — победит последний пришедший запрос. То же время и те же значения — это no-op; то же время с другими значениями — equal_timestamp_conflict.

Позиция требует sku и хотя бы одного из price, stock, moq, discontinued. Всё, что вы не отправили, остаётся как есть — поэтому проход только по остаткам ни разу не тронет цену. Один sku отклоняется, а не считается применённым.

Отправляйте updated_at — и повторы перестают быть опасными: позиция, чьё время старее последнего применённого, ничего не меняет и возвращается как stale_update. Не отправляйте — победит последний пришедший запрос: для одного планового задания это нормально, для двух — нет.

unchanged — это позиции, у которых предложение уже держало отправленные значения. Ничего не записано и ничего не сломано: ночная выгрузка, отправляющая всё, получает applied за изменившееся и unchanged за остальное, а не тысячи ошибок. Тот же updated_at с теми же значениями попадает сюда же; с другими значениями это equal_timestamp_conflict, потому что одинаковое время не может сказать, какая версия актуальна.

Снятие с продажи и возврат не симметричны, и лучше знать почему, чем выяснить это на практике. discontinued: true проходит всегда — убрать собственное предложение не требует ничьего разрешения. discontinued: false возвращает его только если предложение полное и ваш аккаунт вправе публиковать; иначе строка возвращается как relist_blocked с причиной в detail.

not_published — количество применённых строк на предложениях, которых фермер не видит: черновик, приостановленное предложение. Цена действительно изменилась, но на неё никто не смотрит. Патч никогда не публикует за вас, поэтому этот показатель — то, как вы об этом узнаёте, а не гадаете, почему ничего не сдвинулось.

{
  "request_id": "1c9d4b7e-3f60-4a21-8de5-7a2b6c0f9e11",
  "applied": 1198,
  "unchanged": 613,
  "not_published": 42,
  "rejected": [
    {
      "index": 17,
      "sku": "ZIR-20",
      "reason": "sku_ambiguous",
      "detail": "7 of your offers carry this code: Зірочка Z-20, Зірочка Z-22, …"
    },
    {
      "index": 63,
      "sku": "KRB-46",
      "reason": "stale_update",
      "detail": "last applied 2026-08-25T09:00:00+00"
    }
  ],
  "rejected_total": 2
}
КодОбластьЗначение
no_change_requestedодна строка

Только sku, без изменяемых полей. Один timestamp — не изменение: отправьте price, stock, moq или discontinued.

discontinued_invalidодна строка

Присутствует, но не булево. "yes" — это не true.

sku_unknownодна строка

Для этого кода нет активной связи. Отправьте позицию через POST /v1/products один раз — после подтверждения PATCH работает всегда.

sku_pending_catalogодна строка

Вы уже отправляли этот код, и позиция ждёт нашу каталожную команду. Не отправляйте повторно — PATCH начнёт работать с этим кодом сразу после создания товара.

sku_ambiguousодна строка

Этот код есть у нескольких ваших предложений, поэтому неясно, какую цену менять. detail перечисляет товары. Дайте им разные коды в своей системе.

offer_missingодна строка

Связь есть, но её предложение исчезло. Отправьте позицию через POST /v1/products ещё раз.

stale_updateодна строка

Ваш updated_at старее последнего применённого, поэтому ничего не изменилось. detail содержит наш timestamp.

equal_timestamp_conflictодна строка

Ваш updated_at совпадает с уже применённым, но значения другие — мы не можем определить, какое актуально, и отказываем, а не угадываем. Сдвиньте updated_at. Если значения совпадают, позиция идёт в unchanged и ошибки нет.

relist_blockedодна строка

discontinued: false отклонён проверкой публикации. Снятие с продажи проходит всегда; возврат требует полного предложения и права публиковать. detail содержит причину.

price_tieredодна строка

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

Только коды, которые возвращает лишь этот эндпоинт. Все коды уровня запроса и все проверки значений из таблицы выше действуют и здесь, а field называет sku там, где выгрузка товаров назвала бы десяток полей.

Автообновление — мы забираем выгрузку сами

Тот же контракт, что в PATCH /v1/offers — та же структура запроса, те же поля позиции, те же коды причин, тот же результат. Разница только в направлении: не ваша система обращается к нам, а мы раз в сутки обращаемся к URL на вашей стороне. Больше в конвейере не меняется ничего. Настраивается в Настройки → Интеграции.

Создавать товары оно не может. Артикул, которого мы не знаем, отклоняется как sku_unknown. Новые позиции так же идут через POST /v1/products и его очередь проверки; автообновление меняет цену и остаток на уже связанных позициях — и больше ничего.

Тело — то же, что вы уже отправляете в PATCH /v1/offers: массив items, объекты которого несут sku плюс price, stock, moq, discontinued и updated_at. Хотя бы одно из price, stock, moq, discontinued на позицию, иначе позиция отклоняется как no_change_requested. Нераспознанное поле отклоняет весь запрос как unknown_field — сознательно, а не тихим отбрасыванием. Максимум 5000 позиций (too_many_items), а одна партия свыше 16 MiB JSON — это payload_too_large.

Раз в сутки. Вы выбираете час, от 00 до 23, и часовой пояс, в котором он считается — поэтому 03:00 значит 03:00 там, где работает ваша система, а переход на летнее время и обратно мы учитываем сами. Следующий запуск показан на карточке. Выбирайте час, к которому ваша выгрузка уже записана, а не час, когда она начинается.

Аутентификация необязательна: либо ничего, либо один заголовок Authorization: Bearer <token>, который вы задаёте в Настройки → Интеграции. Токен хранится в зашифрованном виде, отправляется на ваш сервер при каждом обращении и никогда не попадает ни в лог, ни в аргументы процесса. 401 или 403 от вас — это feed_http_error с этим статусом, а не наш код аутентификации.

Требования к вашему URL

Только https:// — обычный HTTP отклоняется до того, как будет установлено соединение. Порт 443 или без порта вовсе: мы закрепляем адрес, в который разрешилось ваше доменное имя, через curl --resolve host:443:<address>, и это закрепление действует на пару (хост, порт) — поэтому URL на другом порте ушёл бы в обычный DNS в момент обращения и полностью отменил бы закрепление. Указывайте доменное имя, а не адрес — никаких IP-литералов и никакой их шестнадцатеричной, восьмеричной или целочисленной формы (https://0x7f000001/ отклоняется), потому что клиент считает их адресом и не разрешает их вовсе.

Действующий TLS-сертификат, соответствующий этому доменному имени. Мы проверяем субъект сертификата, а не только его цепочку и срок действия — сертификат, действительный для другого имени, будет отклонён. И никаких перенаправлений: мы отправляем --max-redirs 0, поэтому 3xx возвращается вам как feed_http_error с его статусом. Отдавайте файл по тому URL, который вы нам дали.

Обычный GET — мы не отправляем ни тела, ни своих параметров запроса. Ответ должен быть корректным UTF-8; всё остальное — это bad_encoding, а Windows-1251 — типичная ошибка выгрузок из украинских и российских учётных систем. Предел — 16 MiB: выше него загрузка останавливается и вы получаете too_large. На ответ у вас 20 секунд; на разрешение вашего доменного имени перед этим отведены отдельные шесть секунд. Не кешируйте выгрузку надолго за CDN — мы забираем её раз в сутки, поэтому Cache-Control с max-age на часы или дни означает, что мы читаем вчерашний файл, а ваши цены выглядят застывшими. Ставьте короткий max-age или не ставьте его вовсе.

Когда вы сохраняете выгрузку, мы разрешаем ваше доменное имя и записываем адрес. При каждом следующем обращении мы разрешаем его снова и соединяемся только с тем адресом, который ваш собственный DNS публикует для этого имени сейчас, отдавая предпочтение записанному, пока он среди них. Два следствия, которые стоит назвать: переезд хостинга не требует от вас никаких действий, если DNS обновлён, и мы никогда не соединимся с адресом, который ваш DNS сейчас не называет. Если все адреса, которые он публикует, приватные, loopback, carrier-NAT, link-local или из зарезервированного пространства, запуск останавливается с host_not_routable; если он не публикует ни одного — pin_unresolvable.

Когда запуск не удался

Каждый запуск оставляет строку в журнале запросов на экране интеграций, помеченную как пришедшую из автообновления, с подсчётами и отклонениями. Ошибка, с которой вы можете что-то сделать, дополнительно отправляет вам уведомление в приложении.

Сбои на нашей стороне — потерянная аренда запуска, наша база данных — сознательно не отправляют уведомление и помечены как наши, потому что это не то, что вы можете исправить, а следующий запланированный запуск повторяет попытку сам.

КодОбластьЗначение
bad_urlвесь запрос

Сохранённый URL больше не действителен. Введите его заново.

bad_pinвесь запрос

Не удалось прочитать сохранённый адрес. Проверьте его ещё раз и сохраните, чтобы заменить.

pin_unresolvableвесь запрос

Ваше доменное имя сейчас вообще не разрешается. Проверьте свой DNS, затем проверьте адрес ещё раз.

host_not_routableвесь запрос

Ваше доменное имя разрешается, но все адреса, которые оно сейчас публикует, — те, к которым мы не обращаемся: приватные, loopback, carrier-NAT, link-local или зарезервированные. Направьте доменное имя на публичный адрес.

bad_authвесь запрос

Сохранённый токен не удалось использовать. Введите его заново и сохраните.

too_largeвесь запрос

Ответ больше, чем мы принимаем. Разделите его или уберите ненужные поля.

timeoutвесь запрос

Ваш сервер не ответил за отведённое время.

fetch_failedвесь запрос

Не удалось соединиться с вашим сервером.

feed_http_errorвесь запрос

Ваш сервер ответил HTTP-ошибкой. Код 401 или 403 означает, что он не принял наш токен.

bad_encodingвесь запрос

Ответ не является корректным UTF-8. Отдавайте файл в UTF-8.

malformed_jsonвесь запрос

Ответ не является корректным JSON.

payload_too_largeвесь запрос

Одна партия вашей выгрузки была слишком большой, чтобы мы могли её применить. Уберите ненужные поля или разделите файл.

feed_not_foundвесь запрос

Настройки пропали, пока запуск ещё выполнялся. Сохраните их заново; если это повторяется, обратитесь в поддержку.

lease_expiredвесь запрос

Мы начали запуск и вынуждены были прервать его до завершения. С вашей выгрузкой всё в порядке — следующий запланированный запуск попробует снова.

lease_lostвесь запрос

Это же задание параллельно подхватил другой запуск, поэтому мы откатили изменения и ничего не применили. С вашей выгрузкой всё в порядке — следующий запланированный запуск попробует снова.

internal_errorвесь запрос

Что-то сломалось на нашей стороне при применении данных. С вашей выгрузкой всё в порядке — следующий запланированный запуск попробует снова.

lock_timeoutвесь запрос

Что-то сломалось на нашей стороне при применении данных. С вашей выгрузкой всё в порядке — следующий запланированный запуск попробует снова.

statement_timeoutвесь запрос

Что-то сломалось на нашей стороне при применении данных. С вашей выгрузкой всё в порядке — следующий запланированный запуск попробует снова.

Коды, которые даёт только автообновление — они описывают сам запрос, до того как прочитана хоть одна позиция. Все коды из таблиц PATCH /v1/offers действуют для содержимого без изменений, потому что это то же содержимое. Последние три — наши, не ваши: следующий запланированный запуск повторит сам, и уведомления о них вы не получите.

Категории, которые мы распознаём

Читается из каталога живьём, поэтому этот список не может разойтись с тем, что принимает резолвер. Регистр и конечная пунктуация не важны.

  • seedsСемена

    Hybrids · Seed · seeds · Sowing material · Гибриды · Гібриди · Кукурудза · Кукуруза · Насіннєвий матеріал · Насіння · Насіння кукурудзи · Насіння рапсу · Насіння сої · Насіння соняшника · Посевной материал · Посівний матеріал · Пшеница · Пшениця · Рапс · Семена · Семена кукурузы · Семена подсолнечника · Семена подсолнуха · Семена рапса · Семена сои · Семенной материал · Соняшник · Соя · Ячмень · Ячмінь

  • fertilizersУдобрения

    Fertiliser · Fertilizer · fertilizers · Growth stimulants · Micro fertilizers · Агрохимия · Агрохімія · Добрива · Микроудобрения · Микроудобрения и стимуляторы роста · Минеральные удобрения · Мікродобрива · Мікродобрива та стимулятори росту · Мінеральні добрива · Органические удобрения · Органічні добрива · Стимулятори росту · Стимуляторы роста · Удобрения

  • pesticidesСредства защиты растений

    Agrochemicals · CPP · Crop Protection · Fungicide · Fungicides · Herbicide · Herbicides · Insecticide · Insecticides · Pesticide · pesticides · Plant protection · Агрохимикаты · Агрохімікати · Адъюванты · Адьюванти · Акарициди · Акарициды · Гербицид · Гербициды · Гербіцид · Гербіциди · Десиканти · Десиканты · Засоби захисту · Засоби захисту рослин · ЗЗР · Инсектицид · Инсектициды · Інсектицид · Інсектициди · Пестициди · Пестициди та агрохімікати · Пестициды · Пестициды и агрохимикаты · Прилипатели · Прилипачі · Прилипачі (пав) · Протравители · Протруйник · Протруйники · Родентициди · Родентициды · СЗР · Средства защиты · Средства защиты растений · Фунгицид · Фунгициды · Фунгіцид · Фунгіциди

  • fuel-lubricantsГорюче-смазочные материалы

    Diesel · Fuel · fuel-lubricants · Grease · Lubricants · Oils · Антифриз · Бензин · Горюче-смазочные материалы · ГСМ · Дизельне пальне · Дизельное топливо · ДТ · Масла · Мастила · Пальне · Пально-мастильні матеріали · ПММ · Смазки · Топливо

  • machineryТехника

    Combines · Equipment · Implements · machinery · Tractors · Комбайни · Комбайны · Обладнання · Оборудование · Прицепы · Причепи · Сельхозтехника · Сільгосптехніка · Техника · Техніка · Трактори · Тракторы

  • spare-partsЗапчасти

    Bearings · Belts · Filters · Parts · spare-parts · Детали · Деталі · Запчасти · Запчастини · Ножи · Ножі · Підшипники · Подшипники · Ремені · Ремни · Фильтры · Фільтри

  • tires-wheelsШины и диски

    Rims · Tires · tires-wheels · Tyres · Wheels · Диски · Колеса · Колёса · Покришки · Покрышки · Шини · Шини та диски · Шины · Шины и диски

  • suppliesРасходные материалы

    supplies · Витратні матеріали · Расходные материалы · Спецодежда · Спецодяг · Тара · Упаковка

  • servicesУслуги

    services · Послуги · Сервис · Сервіс · Услуги

  • livestockЖивотноводство

    livestock · Ветпрепарати · Ветпрепараты · Животноводство · Корма · Корми · Тваринництво

  • energy-systemsЭнергосистемы

    energy-systems · Генератори · Генераторы · Енергосистеми · Солнечные панели · Сонячні панелі · Энергосистемы

Единицы, которые мы распознаём

  • piece

    шт · штука · штук · штуки · од · одиниця · pcs · pc · piece · unit

  • kg

    кг · кілограм · килограмм · кілограмів · kg

  • ton

    т · t · тонна · тонн · тонни · ton · tonne

  • liter

    л · l · літр · литр · літрів · liter · litre

  • bag

    мішок · мешок · мішків · bag

  • pack

    уп · пак · пачка · упаковка · паковання · pack

  • canister

    каністра · канистра · кан · canister

  • bottle

    пляшка · бутылка · флакон · bottle

  • set

    набір · набор · set

  • hectare

    га · гектар · гектарів · ha · hectare

Лимиты и идемпотентность

  • POST /v1/products — 5000 × 10 min · 30 req / 10 min
  • PATCH /v1/offers — 5000 × 60 s · 30 req / 60 s
  • 20000 unresolved rows · 10 open jobs

Отправляйте Idempotency-Key на каждом настоящем вызове. Повтор с тем же ключом возвращает сохранённый результат — то же задание, те же отклонения — и ничего не создаёт, так что таймаут на вашей стороне никогда не станет вторым прайсом. Повтор несёт первые 20 отклонений и rejected_total; остальное берите из эндпоинта отклонений.

Открыть в своём инструменте

Контракт выше также опубликован как документ OpenAPI 3.1, сгенерированный из того же источника — поэтому он не может разойтись с этой страницей. Наведите на него любой клиент и получите конструктор запросов, валидацию и консоль Try it, которую мы сами не пишем.

https://adam.ua/ru/docs/api/openapi.json
https://adam.ua/ru/docs/api.md
  • Postman · Insomnia · Bruno — Import → Link, вставьте URL
  • Swagger Editor — File → Import URL
  • Генераторы клиентов — openapi-generator, oazapfts, openapi-typescript

Чтобы отправить настоящий запрос, нужен рабочий ключ. Делайте это в Настройки → Интеграции → Песочница, где ключ остаётся в вашей сессии, а не на странице, которую может открыть кто угодно.

Что происходит дальше

Принятые строки сопоставляются с нашим каталогом и попадают на ваш экран проверки. Позицию, которую мы распознали, вы подтверждаете там; ту, которую не распознали, становится заявкой на новый товар каталога. Проверка — шаг человека, и он не мгновенный: планируйте первую выгрузку с учётом этого. Это также однократно: когда позиция связана, её цена и остаток идут через PATCH /v1/offers и больше никогда не встают в очередь на проверку.

Пока не поддерживается

currency, vat_rate и lead_time_days не читаются ни на одном из эндпоинтов, поэтому их отправка — ошибка, а не тихое отбрасывание. Ступени цены по количеству через API тоже не редактируются: предложение со ступенями отклоняет фиксированную price как price_tiered.

+380 (67) 419-07-94

Contentsquare хранит данные в ЕС (Ирландия). Google Analytics передаёт данные в США — на основании Стандартных договорных положений ЕС.