Підключення власних систем

Чотири способи, якими ваші ціни, ваші виробничі можливості та ваші вироби можуть потрапити на цей майданчик — введені вручну, надіслані вашою системою, запитані нашою або зчитані з крамниці, яку ви вже ведете.

Чотири способи бути в каталозі

Це рівноправні варіанти, а не рівні. Вибір залежить від того, чи є у вас система, варта підключення, і від того, як часто справді змінюється те, що ви продаєте. Ніхто не отримує нижчу оцінку за ручне введення прайсу.

ШляхСкільки коштує запускХто підтримує актуальністьКоли оновлюєтьсяКому підходить
🖥️ Ви вводите тут Нічого. Це форма.Ви, вручнуКоли згадаєте це виправитиМайстерня, чий прайс змінюється двічі на рік. Це також єдиний із чотирьох шляхів, який працює сьогодні.
📤 Ви надсилаєте нам 🚧Одне заплановане завдання у вашій системі, що надсилає файлВи. Ваша система лишається джерелом істини.Так часто, як ви надсилаєтеУ вас уже є складська чи ERP-система, а ціни змінюються швидше, ніж ви хотіли б їх передруковувати.
📥 Ми питаємо вас 🚧Ви тримаєте один ендпоінт, а ми його викликаємоВи, як частину роботи власного сервісуНа кожне замовлення, у мить запитуВаша ціна справді залежить від роботи — від накладу, матеріалу, від того, наскільки заповнений наступний тиждень.
🌐 Ми читаємо вашу крамницю 🚧Ви надсилаєте посилання й відповідаєте на кілька питаньМи — і ми помилятимемосьКоли наступного разу перечитаємо сторінкуУ вас уже є крамниця й жодного бажання займатися будь-чим із переліченого вище.

Спільне для будь-якого шляху через API

🔑 Автентифікація

Пропозиція: bearer-токен, що видається з вашої панелі постачальника й обмежений одним обліковим записом, якому належить — тим самим, під яким ви вже входите, бо стати тут постачальником ніколи не створювало другого запису. Запити, які ми надсилаємо вам, підписувалися б у зворотний бік: HMAC сирого тіла в заголовку X-Personali-Signature, щоб ви могли впевнитися, що запит наш, і при цьому ми не зберігали жодного вашого секрету.

Що саме тут доречне — простий токен, OAuth чи взаємний TLS — справді не вирішено, і постачальник, який уже інтегрувався з кількома майданчиками, має тут кориснішу думку, ніж ми.

♻️ Надсилання того самого двічі

Записи в каталог — це upsert за полем sku, вашим власним ідентифікатором позиції, який ми зберігаємо й ніколи не переписуємо. Повторне надсилання того самого sku редагує цю позицію, а не створює другу, тож перезапустити синхронізацію, що обірвалася на півдорозі, безпечно, і вам ніколи не доведеться тримати таблицю наших ідентифікаторів поряд зі своїми.

Позиція, яку ви перестали надсилати, не видаляється. Мовчання тут не є відкликанням — те саме правило решта майданчика вже застосовує до постачальника, який залишив поле розміру порожнім. Зняття позиції — це явний статус withdrawn, тож обрізаний файл ніколи тихо не спорожнить ваш каталог.

⚠️ Коли щось не проходить перевірку

По позиціях, а не по запиту. Сорок позицій із двома хибними рядками збережуть тридцять вісім і повідомлять про два, бо відхилення цілого файлу через одну одруківку лишає ваш каталог застарілим, поки хтось випадково не помітить. Відповідь називає позицію вашим власним sku, а поле — його шляхом.

Значення, якого ми не впізнаємо — матеріал без ідентифікатора в нас, етап виробництва, якого ми не робимо — повідомляється як невпізнане, а не відкидається. Тихо проігнороване поле — це саме той шлях, яким постачальник починає вірити, що опублікував те, чого не публікував.

📤 Шлях 2 — надсилання каталогу нам

Два виклики. Один каже, хто ви й скільки берете; другий каже, що ви виробляєте. Обидва повторюють структури, які застосунок уже використовує всередині — тому поля читаються як форма заявки. Саме нею вони і є.

Крок 1 — ваш профіль постачальника

role і category визначають, які запити ви взагалі побачите. price і turnaroundDays — ваша базова ставка. priceBreaks — це ваша шкала за обсягом як явний перелік порогів, які ви обрали самі, а не крива, підігнана за вас: пороги виробника є діловим рішенням, яке він може пояснити, і ми радше опублікуємо ваше, ніж вигадаємо своє. city — це те, що надає сенсу самовивозу й друкарням поруч.

sizesOffered, madeToMeasure і maxDimensionsCm — це заява про ваші можливості: які розміри ви тримаєте або шиєте, чи взагалі братиметеся за виріб за мірками клієнта, і яка найбільша річ проходить крізь вашу майстерню. Пропуск будь-якого з них ми вважаємо відсутністю заяви, а не відмовою — постачальник, який заповнив менше форми, не має за це тихо втрачати позиції.

Усі суми — у PLN за одиницю, бо такою є кожна ціна в цьому застосунку. Див. відкриті питання: це справжнє обмеження, а не домовленість, якою ми задоволені.

role             artist | vendor | warehouse | service | seller
category         print | sew | assemble | engrave | transport     (role: vendor only)
sizesOffered     xs | s | m | l | xl | xxl | xxxl | one_size | s_m | l_xl
city             warszawa | krakow | lodz | wroclaw | poznan | gdansk | szczecin |
                 katowice | lublin | rzeszow | kyiv | lviv | odesa | berlin | praha |
                 vilnius | bratislava | amsterdam | paris | madrid
POST /v1/supplier/profile
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "role": "vendor",
  "category": "print",
  "displayName": "PrintHouse Kraków",
  "avatarEmoji": "🖨️",
  "city": "krakow",
  "price": 24,
  "turnaroundDays": 3,
  "priceBreaks": [
    { "minQty": 25,  "unitPrice": 20 },
    { "minQty": 100, "unitPrice": 17 }
  ],
  "description": "DTG and 4-colour screen print. Under 25 pieces we run DTG; above that screen becomes cheaper and we will say so rather than quietly charge the DTG rate.",
  "sizesOffered": ["s", "m", "l", "xl", "xxl"],
  "madeToMeasure": false,
  "maxDimensionsCm": { "length": 200, "width": 120, "height": 60 }
}

Крок 2 — позиції, які ви постачаєте

Позиція тут — це одна конкретна річ, яку можна замовити, всередині одного з наших типів продукту, а не окремий тип продукту. Наш Кухоль — це картка; ваш керамічний кухоль на 450 мл із подвійною стінкою — позиція на ній. Тому кожна позиція називає productId, до якого належить.

priceModifier — це наскільки ваша позиція дорожча за найдешевшу позицію на тій самій картці, у PLN за одиницю. Модифікатор, а не повна ціна, і це навмисно: друк чи пошиття — та сама робота, на якій би позиції цього продукту її не виконували, тож друга повна ціна мусила б переказати весь ланцюг і згодом могла б із ним розійтися. extraLeadDays — це дні, які ваша позиція додає до графіка: кухоль, виточений вручну, не знімають із полиці.

material і personalizations беруться із закритих словників, а не з вільного тексту, бо клієнти за ними фільтрують: «Органічна бавовна» й «органічна бавовна» стали б двома різними фільтрами щойно їх набрали б двоє людей. nameLocalized необов'язкове, і його варто надсилати — вигадана марочна частина назви не перекладається, але «Heavyweight» чи «Hand-Finished» — це факти про товар, і залишити їх англійською означає нічого не сказати польському покупцеві.

stock — єдине поле на цій сторінці, яке сьогодні не має відповідника ніде в застосунку. Воно є у прикладі, бо справжня інтеграція його несла б; у відкритих питаннях пояснено, чому ніхто ще не може сказати, що воно робитиме.

productId        hoodie | tshirt | cap | tote_bag | mug | water_bottle | tumbler |
                 poster | canvas | stickers | cushion | wall_clock | blanket |
                 phone_case | keychain | pen | chair | bookshelf
material         cotton | organic_cotton | cotton_blend | fleece | wool | velvet |
                 canvas | recycled_polyester | recycled_plastic | silicone | leather |
                 ceramic | stainless_steel | brass | bamboo | oak | pine | paper |
                 fine_art_paper | vinyl
personalizations print | embroidery | engraving | foil | handpaint
status           listed | withdrawn
POST /v1/supplier/items
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "items": [
    {
      "sku": "PH-MUG-450-DW",
      "productId": "mug",
      "name": "BrewLine Thermal 450",
      "nameLocalized": { "pl": "BrewLine Termiczny 450", "uk": "BrewLine Термо 450" },
      "material": "ceramic",
      "personalizations": ["print", "engraving"],
      "priceModifier": 14,
      "extraLeadDays": 2,
      "stock": 340,
      "status": "listed"
    },
    {
      "sku": "PH-TEE-ORG-XXXL",
      "productId": "tee",
      "name": "Organic Heavyweight Tee",
      "material": "organic-cotton",
      "personalizations": ["print"],
      "priceModifier": 9,
      "status": "listed"
    }
  ]
}

Що повертається

Статус 207 із переліком кожного надісланого sku й того, що з ним сталося. Відхилений рядок нижче навмисно хибний двічі: одне поле — це майже влучання в справжній словник, друге — неіснуючий ідентифікатор продукту. Обидва називають альтернативи, бо помилка, яка каже лише «ні», коштує комусь половини дня.

HTTP/1.1 207 Multi-Status
Content-Type: application/json

{
  "accepted": 1,
  "rejected": 1,
  "results": [
    {
      "sku": "PH-MUG-450-DW",
      "status": "stored",
      "listedAs": "mug / BrewLine Thermal 450"
    },
    {
      "sku": "PH-TEE-ORG-XXXL",
      "status": "rejected",
      "errors": [
        {
          "field": "productId",
          "code": "unknown_product",
          "message": "No product 'tee'. Apparel ids are hoodie, tshirt, cap, tote_bag.",
          "didYouMean": "tshirt"
        },
        {
          "field": "material",
          "code": "unknown_value",
          "message": "'organic-cotton' is not a material id.",
          "didYouMean": "organic_cotton"
        }
      ]
    }
  ]
}

📥 Шлях 3 — наші запити до вас

Дзеркальне відображення й цікавіший випадок. Замість публікувати прайс і дозволяти нашій арифметиці його застосовувати, ви реєструєте URL, а ми питаємо, скільки коштує одна конкретна робота, у мить, коли її хтось хоче. Ми надсилаємо той самий запит на оцінку, на якому вже працює цей майданчик; у відповідь очікуємо пропозицію — саме те, що постачальник, відповідаючи вручну в панелі, вписує у форму.

Запит на оцінку

POST https://your-system.example/personali/quote
X-Personali-Signature: sha256=9f86d081884c7d659a2feaa0c55ad015
Content-Type: application/json

{
  "requestId": "req_8f2c41",
  "cartId": "cart_31a9d0",
  "target": { "kind": "step", "itemId": "item_7d1", "stepId": "print" },
  "stepLabelKey": "step_label_print",
  "quantity": 120,
  "bulkTargetQuantity": 2500,

  "item": {
    "productId": "tshirt",
    "specificProductId": "tshirt-std",
    "supplierSku": "PH-TEE-ORG-L",
    "size": "l",
    "material": "organic_cotton",
    "personalizations": ["print"]
  },
  "deliverTo": { "city": "warszawa" },
  "respondBy": "2026-08-07T09:41:12Z"
}

Чесне зауваження до цього запиту: requestId, target, stepLabelKey, quantity і bulkTargetQuantity — це поля, які запит на оцінку в застосунку вже несе. item, deliverTo і respondBy — ні. Постачальник, який дивиться в панель, бачить позицію на екрані перед собою, а машина — ні, тож вебхук мусить сказати вголос те, що сторінка лише показує. Позначено окремо, а не вплетено в решту, щоб ніщо тут не читалося як документація чогось наявного.

Ваша відповідь

Одна пастка, варта гучного зауваження: price стосується всього запиту — усіх 120 одиниць — а не однієї одиниці. Така домовленість діє в решті майданчика для ціни етапу, і машина, яка помилково оцінить за одиницю, занизить свою пропозицію на два порядки. bulkUnitPrice — це ціна за одиницю на цільовому обсязі, і саме це число справді вирішує для корпоративного покупця, коли йдеться про пробну партію.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "proposals": [
    {
      "price": 1980,
      "turnaroundDays": 4,
      "bulkUnitPrice": 12.4,
      "note": "Screen print, 3 colours. The 2 500 rate assumes one artwork, no colour change."
    }
  ]
}

Відмова — це повноцінна відповідь

Порожній масив proposals означає «не це замовлення». Для обох сторін це краще, ніж ціна, яку ви воліли б не виконувати, і це саме те, що постачальник робить у панелі щодня, просто не подаючи пропозиції. Це оцінка, а не замовлення: клієнт усе одно порівняє її з усіма іншими й цілком може обрати іншу майстерню.

HTTP/1.1 200 OK
Content-Type: application/json

{ "proposals": [] }

Якщо ваш ендпоінт не відповідає

Оцінка, якої ми не отримаємо за кілька секунд, відкотилася б до вашого опублікованого прайсу, якщо він є, і ні до чого, якщо його немає — не можна лишати клієнта чекати на сервер, який не відповідає. Яким має бути час очікування і чи має ендпоінт, який постійно не встигає, сам себе приглушувати до полагодження, лишається невирішеним.

Або дозвольте нам також забирати каталог

Якщо саме заплановане надсилання є для вас незручною половиною, той самий масив items зі шляху 2 може бути тим, що ми забираємо, а не тим, що ви надсилаєте. Один GET, те саме тіло, ті самі правила upsert. Який із двох шляхів менша робота, залежить винятково від ваших систем, тож пропонуємо обидва, а не називаємо один правильним.

GET https://your-system.example/personali/catalogue
X-Personali-Signature: sha256=1b4f0e9851971998e732078544c96b36
Accept: application/json

→ 200 OK, with a body identical to the "items" payload in route 2 above.

🌐 Ми читаємо вашу крамницю

Що насправді означає читання вашого сайту

Надійно прочитати довільну крамницю неможливо, і ми не називатимемо це синхронізацією. Хоч би що тут постало, воно буде почасти парсером, а почасти людиною: перший прохід по ваших сторінках, потім хтось із нашого боку зіставляє знайдене з нашими категоріями, етапами виробництва, матеріалами й розмірами, а потім ви затверджуєте результат, перш ніж щось стане доступним.

Оголошення, створене так, усе одно ваше. Ви можете виправити його у звичайній формі, і виправлення завжди має перевагу над тим, що ми прочитаємо пізніше — постачальник, який виправив хибну ціну, не має дивитися, як вона вночі повертається.

Що нам було б потрібно від вас

  • Адреса крамниці й дозвіл її читати.
  • Які саме з наших етапів виробництва ви справді виконуєте — сторінка крамниці показує готові вироби й нічого не каже про те, яка частина ланцюга ваша.
  • Чи включають показані ціни ПДВ, бо сторінка рідко це зазначає, а хибне припущення — це помилка на 23%.
  • Людина, якій написати, коли зіставлення виявиться хибним — а іноді так і буде.

Свідомо невирішене

Назвати це вголос — і є сенсом ранньої публікації. Кожен пункт — рішення, яке слід ухвалювати разом із постачальниками, а не подавати їм готовим.

  • Обмеження частоти й розміру запиту. Нічого не вирішено. Постачальник із сорока тисячами позицій і постачальник із дванадцятьма тут однаково ймовірні, а обрати розмір сторінки до того, як з'явиться бодай один із них, означало б лише число, від якого потім відмовлятися.
  • Складські залишки. Виробництво на замовлення взагалі не має залишку, а єдине місце, де цей майданчик продає готові вироби, відстежує доступність як бронювання на оголошенні, а не як лічильник, який хтось надсилає. Чи має надісланий залишок зменшуватися на замовлення, спливати сам, чи бути суто довідковим — відповіді ще немає.
  • Валюта. Кожна ціна в цьому застосунку — у PLN, і поля валюти немає ніде. Український або німецький постачальник, що інтегрується через API, — це саме той випадок, який це ламає, і полем це не лікується без рішення про те, хто перераховує, за яким курсом і хто несе різницю між оцінкою та рахунком.
  • Сама модель автентифікації, а також те, як ротувати чи відкликати токен, коли хтось звільняється з вашої компанії.
  • Повтори й дублювання запитів, які ми надсилаємо вам: скільки разів, з якими проміжками, і чи має бути безпечно відповісти на той самий запит двічі.
  • Чий запис перемагає. Якщо ви надішлете ціну, а потім виправите її у формі, одне з двох мусить програти. Наше чуття каже, що перемагає останній запис, хай яким шляхом він прийшов — але постачальник, чия ERP щоночі тихо перезаписує ручне виправлення, цілком слушно це ненавидів би, тож питання не закрите.
  • Немає тестового середовища, немає тестових облікових даних і немає машинозчитуваної схеми, бо немає сервера. Коли він з'явиться, тестове середовище прийде раніше за документацію, а не після неї.
  • Парсера зі шляху 4 не існує навіть у начерку. Скільки з нього можна автоматизувати, а скільки завжди буде людиною, яка читає ваші сторінки, — це питання, що вирішує, чи буде цей шлях узагалі запропоновано.

Що ви реально можете зробити сьогодні

Подайте заявку через форму, як усі. У полі опису напишіть, який із цих чотирьох шляхів вам потрібен і яка у вас система — це поле вільного тексту саме тому, що жодне структуроване поле такої відповіді не вмістило б. Воно потрапляє до людини й це єдине на цій сторінці, що взагалі кудись потрапляє.