# Надсилайте свій прайс до ADAM

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

Перевірити payload, нічого не створюючи, можна в Налаштування → Інтеграції → Пісочниця.

- OpenAPI 3.1: https://adam.ua/docs/api/openapi.json
- HTML: https://adam.ua/docs/api

## Як отримати ключ

Налаштування → Інтеграції → Підключити. Секрет показується один раз при створенні й зберігається лише як хеш — тримайте його там, де його читає ваша ERP. Надсилайте як `Authorization: Bearer …`.

```bash
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"}]}'
```

## Позиція, поле за полем — `POST /v1/products`

| Поле | Тип | | Правило |
| --- | --- | --- | --- |
| `name` | string · ≤300 | **обов’язкове** | Назва товару у вашій системі. Обрізається, не може бути порожньою. |
| `brand` | string · ≤120 | **обов’язкове** | Розпізнається матчером у межах категорії. |
| `category` | string · ≤120 | **обов’язкове** | У людському вигляді. Розпізнається за списком нижче. |
| `unit` | string · ≤40 | **обов’язкове** | У людському вигляді: «шт», «шт.», «кг», «т», «каністра» — усі розпізнаються. |
| `price` | number | **обов’язкове** | Більше 0. Число, не рядок; десятковий розділювач — «.». |
| `stock` | number | **обов’язкове** | 0 або більше. Нуль означає «немає в наявності», а не «невідомо». |
| `sku` | string · ≤100 | необов’язкове | Ваш артикул. За схемою необов’язковий, але без нього подальший PATCH не має за чим шукати позицію. |
| `warehouse` | string · ≤200 | необов’язкове | Звіряється з вашими складами — спершу точна назва або місто, потім єдиний склад, назва якого містить це значення. Якщо не збіглося або значення немає взагалі, рядок ляже на ваш склад за замовчуванням: єдиний ваш склад або той, що ви позначили основним. Два кандидати — єдиний випадок, коли ми не вгадуємо: склад не проставляється, повертається `warehouse_ambiguous`. |
| `moq` | number | необов’язкове | Більше 0. За замовчуванням 1. |
| `description` | string · ≤500 | необов’язкове | Вільний текст. Використовується, коли рядок стає заявкою на новий товар каталогу. |
| `updated_at` | rfc3339 | необов’язкове | Коли позиція останній раз змінилася у вашій системі. Дає правило порядку для PATCH. |

Поле, яке ми не читаємо, ламає весь запит, а не відкидається тихо. Тихе відкидання — це те, як ви починаєте вірити, що задали валюту, якої не задавали.

## Відповідь

202, бо рядки прийняті в обробку, а зіставлення й перевірка ще попереду. Відхилення називають ваш власний індекс позиції, тому вивантаження, яке ви правите поетапно, сходиться.

```json
{
  "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
}
```

## Коди причин — `POST /v1/products`

| Код | Область | Значення |
| --- | --- | --- |
| `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`

Коли позицію зв’язано, `PATCH /v1/offers` змінює її ціну й залишок без перевірки та без участі людини. Ви надсилаєте той самий артикул, що вже надсилали у вивантаження товарів, а ми змінюємо пропозицію за ним. Це той виклик, який ваша ERP робить за розкладом.

```bash
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}]}'
```

| Поле | Тип | | Правило |
| --- | --- | --- | --- |
| `sku` | string · ≤100 | **обов’язкове** | Ваш артикул, той самий, що надсилали в POST /v1/products. Єдиний спосіб назвати позицію. |
| `price` | number | необов’язкове | Більше 0. Не надсилайте — ціна залишиться як є. |
| `stock` | number | необов’язкове | 0 або більше. Нуль ставить «немає в наявності» й залишає пропозицію в каталозі. |
| `moq` | number | необов’язкове | Більше 0. Мінімальна партія; одиниця виміру тут не змінюється. |
| `discontinued` | boolean | необов’язкове | true знімає пропозицію з продажу, false повертає. Зняття завжди проходить; повернення може бути відхилене. |
| `updated_at` | rfc3339 | необов’язкове | Коли позиція останній раз змінилася у вашій системі. Надішліть — і запізніла повторна спроба не перезапише новішу ціну; не надішлете — переможе останній запит, що надійшов. Той самий час і ті самі значення — це 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` — кількість застосованих рядків на пропозиціях, яких фермер не бачить: чернетка, призупинена пропозиція. Ціна справді змінилася, але на неї ніхто не дивиться. Патч ніколи не публікує замість вас, тому цей показник — те, як ви про це дізнаєтеся, а не гадаєте, чому нічого не зрушило.

```json
{
  "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
}
```

### Коди причин — `PATCH /v1/offers`

| Код | Область | Значення |
| --- | --- | --- |
| `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
- `sku` + ≥1 of `price`, `stock`, `moq`, `discontinued` per `PATCH /v1/offers` item

Надсилайте `Idempotency-Key` на кожному справжньому виклику. Повтор із тим самим ключем повертає збережений результат — те саме завдання, ті самі відхилення — і нічого не створює, тож таймаут на вашій стороні ніколи не стає другим прайсом. Повтор несе перші 20 відхилень і rejected_total; решту беріть з ендпоїнта відхилень.

## Відкрити у своєму інструменті

Контракт вище також опублікований як документ OpenAPI 3.1, згенерований з того самого джерела — тож він не може розійтися з цією сторінкою. Наведіть на нього будь-який клієнт і отримаєте конструктор запитів, валідацію та консоль `Try it`, якої ми не пишемо самі.

```
https://adam.ua/docs/api/openapi.json
```

- 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`.
